@clipform/mcp-server 1.16.0 → 1.16.1
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 +2 -1
- package/dist/{chunk-D64XBDOX.js → chunk-MB6BZ2JN.js} +154 -441
- package/dist/chunk-MB6BZ2JN.js.map +1 -0
- package/dist/index.js +5 -13
- package/dist/index.js.map +1 -1
- package/dist/server.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-D64XBDOX.js.map +0 -1
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
__commonJS,
|
|
3
3
|
__export,
|
|
4
4
|
__toESM,
|
|
5
|
-
getMcpAuth,
|
|
6
5
|
runWithMcpTool
|
|
7
6
|
} from "./chunk-VWGIVJMQ.js";
|
|
8
7
|
|
|
@@ -21277,23 +21276,22 @@ var NODE_TYPES = {
|
|
|
21277
21276
|
is_active: true,
|
|
21278
21277
|
show_in_results: false,
|
|
21279
21278
|
default_config: {
|
|
21280
|
-
|
|
21281
|
-
|
|
21282
|
-
|
|
21283
|
-
|
|
21284
|
-
],
|
|
21285
|
-
prompt: ""
|
|
21286
|
-
},
|
|
21279
|
+
fields: [
|
|
21280
|
+
{ id: "first_name", type: "first_name", label: "First Name", enabled: true, required: true },
|
|
21281
|
+
{ id: "email", type: "email", label: "Email", enabled: true, required: true }
|
|
21282
|
+
],
|
|
21287
21283
|
consent_items: [
|
|
21288
21284
|
{ id: "default-consent", name: "Privacy policy", label: "I agree to the privacy policy and terms of service", order: 0, type: "consent" }
|
|
21289
21285
|
]
|
|
21290
21286
|
},
|
|
21291
21287
|
config_schema: {
|
|
21292
21288
|
type: "object",
|
|
21289
|
+
required: ["fields"],
|
|
21293
21290
|
properties: {
|
|
21294
21291
|
title: { type: "string" },
|
|
21295
21292
|
fields: {
|
|
21296
21293
|
type: "array",
|
|
21294
|
+
minItems: 1,
|
|
21297
21295
|
items: {
|
|
21298
21296
|
type: "object",
|
|
21299
21297
|
required: ["id", "required"],
|
|
@@ -22140,16 +22138,21 @@ var CONTACT_FIELDS = [
|
|
|
22140
22138
|
{ id: "first_name", label: "First Name", type: "text", placeholder: "Enter first name", order: 1 },
|
|
22141
22139
|
{ id: "last_name", label: "Last Name", type: "text", placeholder: "Enter last name", order: 2 },
|
|
22142
22140
|
{ id: "email", label: "Email Address", type: "email", placeholder: "you@example.com", order: 3 },
|
|
22143
|
-
{ id: "phone", label: "Phone Number", type: "tel", placeholder: "(555) 123-4567", order: 4 }
|
|
22144
|
-
{ id: "company", label: "Company", type: "text", placeholder: "Company name", order: 5 },
|
|
22145
|
-
{ id: "job_title", label: "Job Title", type: "text", placeholder: "Your role", order: 6 },
|
|
22146
|
-
{ id: "website", label: "Website", type: "url", placeholder: "https://example.com", order: 7 },
|
|
22147
|
-
{ id: "linkedin", label: "LinkedIn Profile", type: "url", placeholder: "https://linkedin.com/in/username", order: 8 },
|
|
22148
|
-
{ id: "message", label: "Message", type: "textarea", placeholder: "Your message...", order: 9 }
|
|
22141
|
+
{ id: "phone", label: "Phone Number", type: "tel", placeholder: "(555) 123-4567", order: 4 }
|
|
22149
22142
|
];
|
|
22150
22143
|
var CONTACT_FIELDS_MAP = Object.fromEntries(
|
|
22151
22144
|
CONTACT_FIELDS.map((f) => [f.id, f])
|
|
22152
22145
|
);
|
|
22146
|
+
var KEN_BURNS_PRESETS = [
|
|
22147
|
+
"cinematic",
|
|
22148
|
+
"dramatic",
|
|
22149
|
+
"calm",
|
|
22150
|
+
"documentary",
|
|
22151
|
+
"dreamy",
|
|
22152
|
+
"moody"
|
|
22153
|
+
];
|
|
22154
|
+
var KEN_BURNS_EASINGS = ["ease-in-out", "ease-in", "ease-out", "linear"];
|
|
22155
|
+
var KEN_BURNS_FIT_MODES = ["cover", "blur-pad", "auto"];
|
|
22153
22156
|
|
|
22154
22157
|
// src/lib/schemas.ts
|
|
22155
22158
|
var ACTIVE_NODE_TYPES = Object.entries(NODE_TYPES).filter(([, def]) => def.is_active && !def.is_system).map(([type]) => type);
|
|
@@ -22198,8 +22201,15 @@ function generateConfigSummary(configSchema, typeKey) {
|
|
|
22198
22201
|
}
|
|
22199
22202
|
return parts.length > 0 ? `Config: ${parts.join(", ")}` : "";
|
|
22200
22203
|
}
|
|
22204
|
+
function formatDefaultConfig(defaultConfig) {
|
|
22205
|
+
if (!defaultConfig) return "";
|
|
22206
|
+
const filtered = { ...defaultConfig };
|
|
22207
|
+
delete filtered.options;
|
|
22208
|
+
if (Object.keys(filtered).length === 0) return "";
|
|
22209
|
+
return JSON.stringify(filtered);
|
|
22210
|
+
}
|
|
22201
22211
|
var NODE_TYPES_DESCRIPTION = (() => {
|
|
22202
|
-
const lines = ["Node types:"];
|
|
22212
|
+
const lines = ["Node types (omit config to use defaults where shown):"];
|
|
22203
22213
|
for (const type of ACTIVE_NODE_TYPES) {
|
|
22204
22214
|
const def = NODE_TYPES[type];
|
|
22205
22215
|
let line = `- ${type}: ${def.description || def.label}`;
|
|
@@ -22210,17 +22220,26 @@ var NODE_TYPES_DESCRIPTION = (() => {
|
|
|
22210
22220
|
if (configHint) {
|
|
22211
22221
|
line += `. ${configHint}`;
|
|
22212
22222
|
}
|
|
22223
|
+
const defaultStr = formatDefaultConfig(def.default_config);
|
|
22224
|
+
if (defaultStr) {
|
|
22225
|
+
line += `. Defaults: ${defaultStr}`;
|
|
22226
|
+
}
|
|
22213
22227
|
lines.push(line);
|
|
22214
22228
|
}
|
|
22215
22229
|
return lines.join("\n");
|
|
22216
22230
|
})();
|
|
22217
22231
|
var CONFIG_DESCRIPTION = (() => {
|
|
22218
|
-
const lines = ["Type-specific configuration. Per-type keys:"];
|
|
22232
|
+
const lines = ["Type-specific configuration. Omit to use defaults. Per-type keys:"];
|
|
22219
22233
|
for (const type of ACTIVE_NODE_TYPES) {
|
|
22220
22234
|
const def = NODE_TYPES[type];
|
|
22221
22235
|
const summary = generateConfigSummary(def.config_schema, type);
|
|
22222
22236
|
if (summary) {
|
|
22223
|
-
|
|
22237
|
+
let line = ` ${type}: ${summary}`;
|
|
22238
|
+
const defaultStr = formatDefaultConfig(def.default_config);
|
|
22239
|
+
if (defaultStr) {
|
|
22240
|
+
line += `. Defaults: ${defaultStr}`;
|
|
22241
|
+
}
|
|
22242
|
+
lines.push(line);
|
|
22224
22243
|
}
|
|
22225
22244
|
}
|
|
22226
22245
|
return lines.join("\n");
|
|
@@ -22253,83 +22272,44 @@ var NodeSchema = external_exports.object({
|
|
|
22253
22272
|
});
|
|
22254
22273
|
|
|
22255
22274
|
// src/lib/api-client.ts
|
|
22256
|
-
var
|
|
22257
|
-
|
|
22258
|
-
|
|
22259
|
-
|
|
22260
|
-
|
|
22261
|
-
function setApiKey(key) {
|
|
22262
|
-
_apiKey = key;
|
|
22275
|
+
var API_BASE_URL = process.env.API_URL;
|
|
22276
|
+
if (!API_BASE_URL) {
|
|
22277
|
+
throw new Error(
|
|
22278
|
+
"API_URL must be set (e.g. http://localhost:3003 or https://api.clipform.io)"
|
|
22279
|
+
);
|
|
22263
22280
|
}
|
|
22264
|
-
|
|
22265
|
-
|
|
22266
|
-
|
|
22267
|
-
|
|
22268
|
-
|
|
22269
|
-
url += `?${searchParams.toString()}`;
|
|
22270
|
-
}
|
|
22271
|
-
const headers = {
|
|
22272
|
-
"Content-Type": "application/json"
|
|
22273
|
-
};
|
|
22274
|
-
const mcpAuth = getMcpAuth();
|
|
22275
|
-
if (mcpAuth && INTERNAL_SECRET) {
|
|
22276
|
-
headers["Authorization"] = `Bearer ${INTERNAL_SECRET}`;
|
|
22277
|
-
headers["X-Mcp-User"] = mcpAuth.user_id;
|
|
22278
|
-
headers["X-Mcp-Workspace"] = mcpAuth.workspace_id;
|
|
22279
|
-
if (mcpAuth.tool_name) headers["X-Mcp-Tool"] = mcpAuth.tool_name;
|
|
22280
|
-
} else if (_apiKey) {
|
|
22281
|
-
headers["Authorization"] = `Bearer ${_apiKey}`;
|
|
22282
|
-
}
|
|
22283
|
-
const fetchOptions = { method, headers };
|
|
22284
|
-
if (body && method !== "GET") {
|
|
22285
|
-
fetchOptions.body = JSON.stringify(body);
|
|
22286
|
-
}
|
|
22287
|
-
let response;
|
|
22288
|
-
try {
|
|
22289
|
-
response = await fetch(url, fetchOptions);
|
|
22290
|
-
} catch (err) {
|
|
22281
|
+
var MCP_SERVICE_USER_ID = "00000000-0000-4000-8000-000000000001";
|
|
22282
|
+
function getAuthHeaders() {
|
|
22283
|
+
const serviceSecret = process.env.INTERNAL_SERVICE_SECRET || process.env.CLIPFORM_INTERNAL_SERVICE_SECRET;
|
|
22284
|
+
const workspaceId = process.env.MCP_WORKSPACE_ID;
|
|
22285
|
+
if (serviceSecret && workspaceId) {
|
|
22291
22286
|
return {
|
|
22292
|
-
|
|
22293
|
-
|
|
22294
|
-
|
|
22295
|
-
|
|
22296
|
-
Error: ${err instanceof Error ? err.message : String(err)}`
|
|
22287
|
+
"Authorization": `Bearer ${serviceSecret}`,
|
|
22288
|
+
"X-Mcp-Workspace": workspaceId,
|
|
22289
|
+
"X-Mcp-User": MCP_SERVICE_USER_ID
|
|
22297
22290
|
};
|
|
22298
22291
|
}
|
|
22299
|
-
|
|
22300
|
-
|
|
22301
|
-
|
|
22302
|
-
const data = await response.json();
|
|
22303
|
-
if (!response.ok) {
|
|
22304
|
-
return {
|
|
22305
|
-
ok: false,
|
|
22306
|
-
status: response.status,
|
|
22307
|
-
error: data.error || `API error (${response.status})`
|
|
22308
|
-
};
|
|
22292
|
+
const apiKey = process.env.CLIPFORM_API_KEY;
|
|
22293
|
+
if (apiKey) {
|
|
22294
|
+
return { "Authorization": `Bearer ${apiKey}` };
|
|
22309
22295
|
}
|
|
22310
|
-
|
|
22296
|
+
throw new Error(
|
|
22297
|
+
"No API authentication configured. Set INTERNAL_SERVICE_SECRET + MCP_WORKSPACE_ID, or CLIPFORM_API_KEY."
|
|
22298
|
+
);
|
|
22311
22299
|
}
|
|
22312
|
-
async function
|
|
22313
|
-
const {
|
|
22314
|
-
|
|
22300
|
+
async function callApi(path, options = {}) {
|
|
22301
|
+
const { body, params } = options;
|
|
22302
|
+
const method = options.method || (body ? "POST" : "GET");
|
|
22303
|
+
const prefix = path.startsWith("/internal/") ? "" : "/v1";
|
|
22304
|
+
let url = `${API_BASE_URL}${prefix}${path}`;
|
|
22315
22305
|
if (params) {
|
|
22316
22306
|
const searchParams = new URLSearchParams(params);
|
|
22317
22307
|
url += `?${searchParams.toString()}`;
|
|
22318
22308
|
}
|
|
22319
22309
|
const headers = {
|
|
22320
|
-
"Content-Type": "application/json"
|
|
22310
|
+
"Content-Type": "application/json",
|
|
22311
|
+
...getAuthHeaders()
|
|
22321
22312
|
};
|
|
22322
|
-
const mcpAuth = getMcpAuth();
|
|
22323
|
-
if (mcpAuth && INTERNAL_SECRET) {
|
|
22324
|
-
headers["Authorization"] = `Bearer ${INTERNAL_SECRET}`;
|
|
22325
|
-
headers["X-Mcp-User"] = mcpAuth.user_id;
|
|
22326
|
-
headers["X-Mcp-Workspace"] = mcpAuth.workspace_id;
|
|
22327
|
-
if (mcpAuth.tool_name) headers["X-Mcp-Tool"] = mcpAuth.tool_name;
|
|
22328
|
-
} else if (_apiKey) {
|
|
22329
|
-
headers["Authorization"] = `Bearer ${_apiKey}`;
|
|
22330
|
-
} else if (INTERNAL_SECRET) {
|
|
22331
|
-
headers["Authorization"] = `Bearer ${INTERNAL_SECRET}`;
|
|
22332
|
-
}
|
|
22333
22313
|
const fetchOptions = { method, headers };
|
|
22334
22314
|
if (body && method !== "GET") {
|
|
22335
22315
|
fetchOptions.body = JSON.stringify(body);
|
|
@@ -22341,7 +22321,7 @@ async function callInternalApi(path, options = {}) {
|
|
|
22341
22321
|
return {
|
|
22342
22322
|
ok: false,
|
|
22343
22323
|
status: 0,
|
|
22344
|
-
error: `Failed to connect to
|
|
22324
|
+
error: `Failed to connect to API at ${url}.
|
|
22345
22325
|
|
|
22346
22326
|
Error: ${err instanceof Error ? err.message : String(err)}`
|
|
22347
22327
|
};
|
|
@@ -22354,7 +22334,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`
|
|
|
22354
22334
|
return {
|
|
22355
22335
|
ok: false,
|
|
22356
22336
|
status: response.status,
|
|
22357
|
-
error: data.error || `
|
|
22337
|
+
error: data.error || `API error (${response.status})`
|
|
22358
22338
|
};
|
|
22359
22339
|
}
|
|
22360
22340
|
return { ok: true, data };
|
|
@@ -22430,14 +22410,27 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
22430
22410
|
workspace_id: workspaceId,
|
|
22431
22411
|
workspace_name: me.workspace?.name ?? null,
|
|
22432
22412
|
plan_name: me.plan?.name ?? "Free",
|
|
22433
|
-
node_limit: me.plan?.node_limit ?? null
|
|
22413
|
+
node_limit: me.plan?.node_limit ?? null,
|
|
22414
|
+
form_limit: me.plan?.form_limit ?? null
|
|
22434
22415
|
};
|
|
22435
|
-
const contentCount = nodes.filter((q) => q.type
|
|
22416
|
+
const contentCount = nodes.filter((q) => !NON_COUNTABLE_TYPES.includes(q.type)).length;
|
|
22436
22417
|
if (planContext.node_limit !== null && contentCount > planContext.node_limit) {
|
|
22437
22418
|
const upgradeUrl = `${BUSINESS.urls.dashboard}/billing`;
|
|
22438
22419
|
const message = planContext.auth_mode === "oauth" ? `Your '${planContext.workspace_name}' workspace is on the ${planContext.plan_name} plan, capped at ${planContext.node_limit} nodes per form. You asked for ${contentCount}. Either rerun with ${planContext.node_limit} nodes or upgrade at ${upgradeUrl}.` : `Anonymous sessions are capped at ${planContext.node_limit} nodes per form (${planContext.plan_name} plan). You asked for ${contentCount}. Either rerun with ${planContext.node_limit} nodes, or connect your Clipform account in claude.ai \u2192 Settings \u2192 Connectors so forms land in your workspace with your real plan limits.`;
|
|
22439
22420
|
return errorResult(message);
|
|
22440
22421
|
}
|
|
22422
|
+
if (planContext.form_limit !== null) {
|
|
22423
|
+
const formsResult = await callApi("/forms", { method: "GET" });
|
|
22424
|
+
if (formsResult.ok) {
|
|
22425
|
+
const formCount = Array.isArray(formsResult.data) ? formsResult.data.length : 0;
|
|
22426
|
+
if (formCount >= planContext.form_limit) {
|
|
22427
|
+
const upgradeUrl = `${BUSINESS.urls.dashboard}/billing`;
|
|
22428
|
+
return errorResult(
|
|
22429
|
+
`Your workspace has reached the ${planContext.plan_name} plan limit of ${planContext.form_limit} forms. Upgrade at ${upgradeUrl} for unlimited forms.`
|
|
22430
|
+
);
|
|
22431
|
+
}
|
|
22432
|
+
}
|
|
22433
|
+
}
|
|
22441
22434
|
const createResult = await callApi("/forms", {
|
|
22442
22435
|
method: "POST",
|
|
22443
22436
|
body: { title, workspace_id: workspaceId }
|
|
@@ -22462,6 +22455,11 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
22462
22455
|
});
|
|
22463
22456
|
}
|
|
22464
22457
|
for (const q of nodes) {
|
|
22458
|
+
if (q.type === "button" && (!q.options || q.options.length === 0)) {
|
|
22459
|
+
const buttonDefault = NODE_TYPES.button?.config_schema?.properties?.button_text?.default || "Continue";
|
|
22460
|
+
const label = q.config?.button_text || buttonDefault;
|
|
22461
|
+
q.options = [{ content: label }];
|
|
22462
|
+
}
|
|
22465
22463
|
const addResult = await callApi(`/forms/${formId}/nodes`, {
|
|
22466
22464
|
method: "POST",
|
|
22467
22465
|
body: { node: q }
|
|
@@ -22470,10 +22468,13 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
22470
22468
|
return errorResult(`Failed to add node "${q.prompt}": ${addResult.error}`);
|
|
22471
22469
|
}
|
|
22472
22470
|
}
|
|
22473
|
-
await callApi(`/forms/${formId}`, {
|
|
22471
|
+
const publishResult = await callApi(`/forms/${formId}`, {
|
|
22474
22472
|
method: "PATCH",
|
|
22475
22473
|
body: { is_live: true }
|
|
22476
22474
|
});
|
|
22475
|
+
if (!publishResult.ok) {
|
|
22476
|
+
return errorResult(`Form created but failed to publish: ${publishResult.error}. Form ID: ${formId}`);
|
|
22477
|
+
}
|
|
22477
22478
|
if (tags && tags.length > 0) {
|
|
22478
22479
|
await callApi(`/forms/${formId}/tags`, {
|
|
22479
22480
|
method: "PUT",
|
|
@@ -22498,7 +22499,9 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
22498
22499
|
`Pass form_id on follow-up tools (get_form, add_node, upload_node_media, etc.).`
|
|
22499
22500
|
);
|
|
22500
22501
|
if (planContext) {
|
|
22501
|
-
const
|
|
22502
|
+
const nodePart = planContext.node_limit === null ? "unlimited nodes" : `up to ${planContext.node_limit} nodes per form`;
|
|
22503
|
+
const formPart = planContext.form_limit === null ? "unlimited forms" : `up to ${planContext.form_limit} forms`;
|
|
22504
|
+
const limitNote = `${formPart}, ${nodePart}`;
|
|
22502
22505
|
if (planContext.auth_mode === "oauth") {
|
|
22503
22506
|
lines.push(
|
|
22504
22507
|
``,
|
|
@@ -22779,11 +22782,7 @@ function registerAddNodeTool(server) {
|
|
|
22779
22782
|
"clipform_add_node",
|
|
22780
22783
|
{
|
|
22781
22784
|
title: "Add Node",
|
|
22782
|
-
description: `Add a new node to an existing form.
|
|
22783
|
-
|
|
22784
|
-
${NODE_TYPES_DESCRIPTION}
|
|
22785
|
-
|
|
22786
|
-
All type definitions and config schemas are derived from @vid-master/config (answer-types).`,
|
|
22785
|
+
description: `Add a new node to an existing form. Inserted before the end screen by default. Use after_node_id to insert at a specific position. Node types and config schemas match clipform_create_form.`,
|
|
22787
22786
|
inputSchema: {
|
|
22788
22787
|
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
22789
22788
|
node: NodeSchema.describe("The node to add"),
|
|
@@ -22932,6 +22931,7 @@ ${formState}` : confirmMsg);
|
|
|
22932
22931
|
}
|
|
22933
22932
|
|
|
22934
22933
|
// src/tools/upload-node-media.ts
|
|
22934
|
+
var MEDIA_SUPPORTED_TYPES = Object.entries(NODE_TYPES).filter(([, def]) => def.supports_media).map(([type]) => type);
|
|
22935
22935
|
var MediaItemSchema = external_exports.object({
|
|
22936
22936
|
node_id: external_exports.string().describe("The node ID"),
|
|
22937
22937
|
media_type: external_exports.enum(["video", "still"]).describe("Type of media"),
|
|
@@ -22960,7 +22960,7 @@ function registerUploadNodeMediaTool(server) {
|
|
|
22960
22960
|
title: "Upload Node Media",
|
|
22961
22961
|
description: `Upload media for one or more nodes. Pass one item or many (max 10). Multiple items upload sequentially.
|
|
22962
22962
|
|
|
22963
|
-
When a public URL is provided, the media is fetched and stored automatically. Only works on node types that support media (
|
|
22963
|
+
When a public URL is provided, the media is fetched and stored automatically. Only works on node types that support media (${MEDIA_SUPPORTED_TYPES.join(", ")}). For video: ingested via Mux. For image: stored in Supabase. When attaching TTS narration video, always include captions from clipform_generate_tts.`,
|
|
22964
22964
|
inputSchema: {
|
|
22965
22965
|
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
22966
22966
|
items: external_exports.array(MediaItemSchema).min(1).max(10).describe("One or more media items to upload")
|
|
@@ -23149,44 +23149,6 @@ ${formState}` : confirmMsg);
|
|
|
23149
23149
|
);
|
|
23150
23150
|
}
|
|
23151
23151
|
|
|
23152
|
-
// src/tools/attach-node-audio.ts
|
|
23153
|
-
function registerAttachNodeAudioTool(server) {
|
|
23154
|
-
server.registerTool(
|
|
23155
|
-
"clipform_attach_audio",
|
|
23156
|
-
{
|
|
23157
|
-
title: "Attach Audio to Node",
|
|
23158
|
-
description: `Attach a sound effect or audio file to a node with existing media (stills only). The audio auto-plays once when a respondent reaches this node. Provide a public URL to the audio file (WAV, MP3, or OGG). The node must already have media uploaded (use clipform_upload_node_media first).`,
|
|
23159
|
-
inputSchema: {
|
|
23160
|
-
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
23161
|
-
node_id: external_exports.string().describe("The node ID (must already have media attached)"),
|
|
23162
|
-
url: external_exports.string().url().describe("Public URL to the audio file (WAV, MP3, or OGG)")
|
|
23163
|
-
},
|
|
23164
|
-
annotations: {
|
|
23165
|
-
readOnlyHint: false,
|
|
23166
|
-
destructiveHint: false,
|
|
23167
|
-
idempotentHint: true,
|
|
23168
|
-
openWorldHint: true
|
|
23169
|
-
}
|
|
23170
|
-
},
|
|
23171
|
-
async ({ form_id, node_id, url }) => {
|
|
23172
|
-
const result = await callApi(
|
|
23173
|
-
`/forms/${form_id}/nodes/${node_id}/audio`,
|
|
23174
|
-
{
|
|
23175
|
-
method: "PUT",
|
|
23176
|
-
body: { url }
|
|
23177
|
-
}
|
|
23178
|
-
);
|
|
23179
|
-
if (!result.ok) {
|
|
23180
|
-
return errorResult(result.error);
|
|
23181
|
-
}
|
|
23182
|
-
return textResult(
|
|
23183
|
-
`Audio attached to node ${node_id}.
|
|
23184
|
-
Audio path: ${result.data.audio_storage_path}`
|
|
23185
|
-
);
|
|
23186
|
-
}
|
|
23187
|
-
);
|
|
23188
|
-
}
|
|
23189
|
-
|
|
23190
23152
|
// src/tools/log-generation.ts
|
|
23191
23153
|
function registerLogGenerationTool(server) {
|
|
23192
23154
|
server.registerTool(
|
|
@@ -23213,7 +23175,7 @@ function registerLogGenerationTool(server) {
|
|
|
23213
23175
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
23214
23176
|
},
|
|
23215
23177
|
async ({ form_id, summary, details }) => {
|
|
23216
|
-
const result = await
|
|
23178
|
+
const result = await callApi("/internal/log-generation", {
|
|
23217
23179
|
body: { form_id, summary, details }
|
|
23218
23180
|
});
|
|
23219
23181
|
if (!result.ok) return errorResult(result.error);
|
|
@@ -23246,7 +23208,7 @@ If neither native web search nor this tool is available and the topic is post-cu
|
|
|
23246
23208
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
23247
23209
|
},
|
|
23248
23210
|
async ({ query, count }) => {
|
|
23249
|
-
const result = await
|
|
23211
|
+
const result = await callApi("/internal/search-news", {
|
|
23250
23212
|
body: { query, count }
|
|
23251
23213
|
});
|
|
23252
23214
|
if (!result.ok) return errorResult(result.error);
|
|
@@ -23305,7 +23267,7 @@ DOES NOT WORK FOR:
|
|
|
23305
23267
|
}
|
|
23306
23268
|
},
|
|
23307
23269
|
async ({ url, lang, max_chars }) => {
|
|
23308
|
-
const result = await
|
|
23270
|
+
const result = await callApi("/internal/youtube-transcript", {
|
|
23309
23271
|
body: { url, lang, max_chars }
|
|
23310
23272
|
});
|
|
23311
23273
|
if (!result.ok) return errorResult(result.error);
|
|
@@ -23357,7 +23319,7 @@ Returns audio URL and word-level captions per item.`,
|
|
|
23357
23319
|
const workspace_id = process.env.MCP_WORKSPACE_ID;
|
|
23358
23320
|
const results = await Promise.allSettled(
|
|
23359
23321
|
items.map(
|
|
23360
|
-
(item) =>
|
|
23322
|
+
(item) => callApi("/internal/tts", {
|
|
23361
23323
|
body: { text: item.text, voice: item.voice, workspace_id }
|
|
23362
23324
|
})
|
|
23363
23325
|
)
|
|
@@ -23395,101 +23357,9 @@ Returns audio URL and word-level captions per item.`,
|
|
|
23395
23357
|
);
|
|
23396
23358
|
}
|
|
23397
23359
|
|
|
23398
|
-
// ../../node_modules/uuid/dist/esm/stringify.js
|
|
23399
|
-
var byteToHex = [];
|
|
23400
|
-
for (let i = 0; i < 256; ++i) {
|
|
23401
|
-
byteToHex.push((i + 256).toString(16).slice(1));
|
|
23402
|
-
}
|
|
23403
|
-
function unsafeStringify(arr, offset = 0) {
|
|
23404
|
-
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();
|
|
23405
|
-
}
|
|
23406
|
-
|
|
23407
|
-
// ../../node_modules/uuid/dist/esm/rng.js
|
|
23408
|
-
import { randomFillSync } from "crypto";
|
|
23409
|
-
var rnds8Pool = new Uint8Array(256);
|
|
23410
|
-
var poolPtr = rnds8Pool.length;
|
|
23411
|
-
function rng() {
|
|
23412
|
-
if (poolPtr > rnds8Pool.length - 16) {
|
|
23413
|
-
randomFillSync(rnds8Pool);
|
|
23414
|
-
poolPtr = 0;
|
|
23415
|
-
}
|
|
23416
|
-
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
23417
|
-
}
|
|
23418
|
-
|
|
23419
|
-
// ../../node_modules/uuid/dist/esm/native.js
|
|
23420
|
-
import { randomUUID } from "crypto";
|
|
23421
|
-
var native_default = { randomUUID };
|
|
23422
|
-
|
|
23423
|
-
// ../../node_modules/uuid/dist/esm/v4.js
|
|
23424
|
-
function v4(options, buf, offset) {
|
|
23425
|
-
if (native_default.randomUUID && !buf && !options) {
|
|
23426
|
-
return native_default.randomUUID();
|
|
23427
|
-
}
|
|
23428
|
-
options = options || {};
|
|
23429
|
-
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
23430
|
-
if (rnds.length < 16) {
|
|
23431
|
-
throw new Error("Random bytes length must be >= 16");
|
|
23432
|
-
}
|
|
23433
|
-
rnds[6] = rnds[6] & 15 | 64;
|
|
23434
|
-
rnds[8] = rnds[8] & 63 | 128;
|
|
23435
|
-
if (buf) {
|
|
23436
|
-
offset = offset || 0;
|
|
23437
|
-
if (offset < 0 || offset + 16 > buf.length) {
|
|
23438
|
-
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
|
|
23439
|
-
}
|
|
23440
|
-
for (let i = 0; i < 16; ++i) {
|
|
23441
|
-
buf[offset + i] = rnds[i];
|
|
23442
|
-
}
|
|
23443
|
-
return buf;
|
|
23444
|
-
}
|
|
23445
|
-
return unsafeStringify(rnds);
|
|
23446
|
-
}
|
|
23447
|
-
var v4_default = v4;
|
|
23448
|
-
|
|
23449
|
-
// src/lib/render-jobs.ts
|
|
23450
|
-
var jobs = /* @__PURE__ */ new Map();
|
|
23451
|
-
var MAX_AGE_MS = 30 * 60 * 1e3;
|
|
23452
|
-
var RENDER_TIMING = {
|
|
23453
|
-
expectedRange: "15-120 seconds",
|
|
23454
|
-
pollDelay: "~15 seconds"
|
|
23455
|
-
};
|
|
23456
|
-
function createJob(tool) {
|
|
23457
|
-
const job = {
|
|
23458
|
-
id: v4_default(),
|
|
23459
|
-
status: "rendering",
|
|
23460
|
-
tool,
|
|
23461
|
-
createdAt: Date.now()
|
|
23462
|
-
};
|
|
23463
|
-
jobs.set(job.id, job);
|
|
23464
|
-
return job;
|
|
23465
|
-
}
|
|
23466
|
-
function completeJob(id, result) {
|
|
23467
|
-
const job = jobs.get(id);
|
|
23468
|
-
if (job) {
|
|
23469
|
-
job.status = "complete";
|
|
23470
|
-
job.result = result;
|
|
23471
|
-
}
|
|
23472
|
-
}
|
|
23473
|
-
function failJob(id, error2) {
|
|
23474
|
-
const job = jobs.get(id);
|
|
23475
|
-
if (job) {
|
|
23476
|
-
job.status = "failed";
|
|
23477
|
-
job.error = error2;
|
|
23478
|
-
}
|
|
23479
|
-
}
|
|
23480
|
-
function getJob(id) {
|
|
23481
|
-
return jobs.get(id);
|
|
23482
|
-
}
|
|
23483
|
-
function pruneJobs() {
|
|
23484
|
-
const cutoff = Date.now() - MAX_AGE_MS;
|
|
23485
|
-
for (const [id, job] of jobs) {
|
|
23486
|
-
if (job.createdAt < cutoff) jobs.delete(id);
|
|
23487
|
-
}
|
|
23488
|
-
}
|
|
23489
|
-
|
|
23490
23360
|
// src/tools/generate-slideshow.ts
|
|
23491
23361
|
var slideshowStyleSchema = external_exports.object({
|
|
23492
|
-
preset: external_exports.enum(
|
|
23362
|
+
preset: external_exports.enum(KEN_BURNS_PRESETS).optional().describe("Style preset: cinematic (default), dramatic, calm, documentary, dreamy, moody."),
|
|
23493
23363
|
zoom: external_exports.object({
|
|
23494
23364
|
from: external_exports.number().optional().describe("Starting scale for zoom-in / ending scale for zoom-out. Default 1.0."),
|
|
23495
23365
|
to: external_exports.number().optional().describe("Ending scale for zoom-in / starting scale for zoom-out. Default 1.3 (cover) / 1.15 (blur-pad).")
|
|
@@ -23503,7 +23373,7 @@ var slideshowStyleSchema = external_exports.object({
|
|
|
23503
23373
|
to: external_exports.number().optional(),
|
|
23504
23374
|
rangeFraction: external_exports.number().optional().describe("Multiplier on pan.range for zoom-in-pan-* effects. Default 0.5.")
|
|
23505
23375
|
}).optional(),
|
|
23506
|
-
easing: external_exports.enum(
|
|
23376
|
+
easing: external_exports.enum(KEN_BURNS_EASINGS).optional().describe("Motion curve. 'ease-in-out' = cinematic default. 'linear' = mechanical/deliberate. 'ease-out' = fast-start slow-finish, good for reveals."),
|
|
23507
23377
|
blurPad: external_exports.object({
|
|
23508
23378
|
blurPx: external_exports.number().optional().describe("Blur strength on the background layer. Default 40. 60+ for heavy dreamy effect, 20 for just-barely."),
|
|
23509
23379
|
brightness: external_exports.number().optional().describe("Background brightness multiplier. Default 0.55 (dimmed). Raise to 0.8+ for a brighter bed; lower for moodier."),
|
|
@@ -23526,75 +23396,20 @@ function registerGenerateSlideshowTool(server) {
|
|
|
23526
23396
|
"clipform_generate_slideshow",
|
|
23527
23397
|
{
|
|
23528
23398
|
title: "Generate Slideshow",
|
|
23529
|
-
description:
|
|
23530
|
-
|
|
23531
|
-
Generate a slideshow video from images and audio. Creates a 9:16 (720x1280) video with smooth pan/zoom effects (Ken Burns style), eased motion, and crossfade transitions, synced to the audio duration. Uploaded to storage; returns a public URL.
|
|
23532
|
-
|
|
23533
|
-
You are the director. Every stylistic choice below is yours - defaults exist for convenience but override anything that fits your creative vision.
|
|
23534
|
-
|
|
23535
|
-
## Workflow
|
|
23536
|
-
|
|
23537
|
-
1. Source images (use clipform_search_media with kind: "image"). Prefer portrait sources for 9:16 output; landscape works too via blur-pad fallback.
|
|
23538
|
-
2. Produce narration audio (clipform_generate_tts).
|
|
23539
|
-
3. Call this tool with images + audio_url + your creative direction.
|
|
23540
|
-
4. Attach the returned public URL to a node via clipform_upload_media with media_type "video".
|
|
23541
|
-
|
|
23542
|
-
## Image framing (per-image)
|
|
23399
|
+
description: `Deprecated: prefer clipform_generate_video which supports both images and video clips.
|
|
23543
23400
|
|
|
23544
|
-
|
|
23545
|
-
- **fit**: 'cover' (fill frame, crop), 'blur-pad' (sharp contained foreground + blurred cover background for landscape sources), 'auto' (default: cover for portrait, blur-pad for landscape > 1.1:1).
|
|
23546
|
-
- **style**: per-image creative overrides (see Style Knobs below).
|
|
23401
|
+
Generate a Ken Burns slideshow video (9:16, 720x1280) from images + audio. Blocks until complete. Returns a public URL.
|
|
23547
23402
|
|
|
23548
|
-
|
|
23403
|
+
Workflow: 1) clipform_search_media (kind: "image") 2) clipform_generate_tts for narration 3) this tool 4) clipform_upload_node_media with media_type "video".
|
|
23549
23404
|
|
|
23550
|
-
|
|
23551
|
-
|
|
23552
|
-
## Transitions
|
|
23553
|
-
|
|
23554
|
-
fade (default), slide, wipe. Legacy names (fadeblack, slideleft, etc.) also work. duration is in seconds.
|
|
23555
|
-
|
|
23556
|
-
## Style presets (fastest path)
|
|
23557
|
-
|
|
23558
|
-
Pick one as a starting point via \`style.preset\` (per-image) or \`default_style.preset\` (global). Override individual knobs on top.
|
|
23559
|
-
|
|
23560
|
-
- **cinematic** - the default feel. Smooth ease-in-out, zoom 1.0\u21921.3, subtle pan, subtle vignette.
|
|
23561
|
-
- **dramatic** - zoom 1.0\u21921.4, ease-out, wider pan (0.07), strong vignette. Good for big-reveal content, competitions, climactic moments.
|
|
23562
|
-
- **calm** - zoom 1.0\u21921.08, linear easing, no vignette. Reflective topics, wellness, educational explainers.
|
|
23563
|
-
- **documentary** - near-static (zoom 1.0\u21921.05, tiny pan, no vignette). Talking-heads-style context shots, historical photos.
|
|
23564
|
-
- **dreamy** - heavy blur-pad (blurPx 60, brightness 0.75), soft vignette. Aspirational, romantic, nostalgic.
|
|
23565
|
-
- **moody** - desaturated blur-pad (brightness 0.35, saturate 0.7), strong vignette, dark wrapper color. Crime, mystery, somber topics.
|
|
23566
|
-
|
|
23567
|
-
Per-image preset wins over default_style preset. Per-field overrides (zoom, pan, etc.) merge on top of whichever preset is active.
|
|
23568
|
-
|
|
23569
|
-
## Style Knobs (per-image via images[i].style, or global via default_style)
|
|
23570
|
-
|
|
23571
|
-
- **zoom**: { from, to } - push-in/pull-out range. Cinematic default 1.0\u21921.3. Drop to 1.0\u21921.05 for near-static; push to 1.0\u21921.4 for dramatic.
|
|
23572
|
-
- **pan**: { range, scale } - drift magnitude. 0.03 subtle, 0.05 default, 0.08+ sweeping.
|
|
23573
|
-
- **easing**: 'ease-in-out' (default, cinematic) | 'ease-in' (accelerate) | 'ease-out' (decelerate, good for reveals) | 'linear' (mechanical).
|
|
23574
|
-
- **blurPad**: { blurPx, brightness, saturate, overscale, foregroundScale } - only applies when blur-pad fit is active. Heavier blurPx (60) + lower brightness (0.35) for moody; foregroundScale: 1.2 to reduce letterbox at the cost of mild crop.
|
|
23575
|
-
- **vignette**: false to disable, or { opacity, innerRadiusPercent }. Stronger vignette (opacity 0.6) for dramatic focus; disable for bright, flat looks.
|
|
23576
|
-
- **backgroundColor**: hex color shown behind the image frame (default '#000').
|
|
23577
|
-
- **autoBlurPadThreshold**: when fit='auto', images wider than this ratio get blur-pad. Default 1.1.
|
|
23578
|
-
|
|
23579
|
-
## Global options
|
|
23580
|
-
|
|
23581
|
-
- **default_style**: style applied to every image unless the image overrides per-field. Per-image style merges over default_style at the field level.
|
|
23582
|
-
- **background_color**: viewport wrapper color, visible during cross-fades.
|
|
23583
|
-
- **random_effects**: shuffles effects across images when true.
|
|
23584
|
-
|
|
23585
|
-
## Creative guidance
|
|
23586
|
-
|
|
23587
|
-
- For the same 5-image quiz slideshow, vary the effects (one zoom-in on the opener, pan-left on a cityscape, zoom-out on the closer) rather than random_effects: true, unless you want the dice-roll.
|
|
23588
|
-
- Quiet/reflective topics: slow easing ('linear'), tight zoom (1.0\u21921.08), no vignette.
|
|
23589
|
-
- Energetic topics: punchy zoom (1.0\u21921.35), ease-out, wider pan.
|
|
23590
|
-
- Mixed orientation sets: rely on fit: 'auto' + pass aspect_ratio per image. The composition will cover-crop portraits and blur-pad landscapes without more work.`,
|
|
23405
|
+
Style presets via default_style.preset: cinematic (default), dramatic, calm, documentary, dreamy, moody. Per-image overrides merge on top. See parameter descriptions for details.`,
|
|
23591
23406
|
inputSchema: {
|
|
23592
23407
|
images: external_exports.array(
|
|
23593
23408
|
external_exports.object({
|
|
23594
23409
|
url: external_exports.string().url().describe("Image URL"),
|
|
23595
23410
|
effect: external_exports.string().optional().describe("Ken Burns effect name (see description). Pass 'random' to shuffle this image only."),
|
|
23596
23411
|
aspect_ratio: external_exports.number().positive().optional().describe("Source image width / height. Enables auto blur-pad for landscape images in the 9:16 viewport."),
|
|
23597
|
-
fit: external_exports.enum(
|
|
23412
|
+
fit: external_exports.enum(KEN_BURNS_FIT_MODES).optional().describe("Framing mode. 'auto' (default) picks cover for portrait, blur-pad for landscape."),
|
|
23598
23413
|
style: slideshowStyleSchema.optional()
|
|
23599
23414
|
})
|
|
23600
23415
|
).min(1).max(20).describe("Images for the slideshow (1-20)"),
|
|
@@ -23611,8 +23426,7 @@ Per-image preset wins over default_style preset. Per-field overrides (zoom, pan,
|
|
|
23611
23426
|
},
|
|
23612
23427
|
async ({ images, audio_url, random_effects, transition, default_style, background_color }) => {
|
|
23613
23428
|
const workspace_id = process.env.MCP_WORKSPACE_ID;
|
|
23614
|
-
const
|
|
23615
|
-
callInternalApi("/internal/slideshow", {
|
|
23429
|
+
const result = await callApi("/internal/slideshow", {
|
|
23616
23430
|
body: {
|
|
23617
23431
|
images,
|
|
23618
23432
|
audio_url,
|
|
@@ -23622,23 +23436,19 @@ Per-image preset wins over default_style preset. Per-field overrides (zoom, pan,
|
|
|
23622
23436
|
background_color,
|
|
23623
23437
|
workspace_id
|
|
23624
23438
|
}
|
|
23625
|
-
}).then((result) => {
|
|
23626
|
-
if (!result.ok) {
|
|
23627
|
-
failJob(job.id, result.error);
|
|
23628
|
-
} else {
|
|
23629
|
-
completeJob(job.id, result.data);
|
|
23630
|
-
}
|
|
23631
|
-
}).catch((err) => {
|
|
23632
|
-
failJob(job.id, err instanceof Error ? err.message : String(err));
|
|
23633
23439
|
});
|
|
23440
|
+
if (!result.ok) return errorResult(result.error);
|
|
23441
|
+
const data = result.data;
|
|
23634
23442
|
return textResult(
|
|
23635
23443
|
[
|
|
23636
|
-
`Slideshow
|
|
23444
|
+
`Slideshow rendered (${images.length} image${images.length > 1 ? "s" : ""}).`,
|
|
23637
23445
|
``,
|
|
23638
|
-
`
|
|
23446
|
+
`Public URL: ${data.public_url}`,
|
|
23447
|
+
`Storage path: ${data.storage_path}`,
|
|
23448
|
+
data.duration_seconds ? `Duration: ${data.duration_seconds}s` : "",
|
|
23639
23449
|
``,
|
|
23640
|
-
`
|
|
23641
|
-
].join("\n")
|
|
23450
|
+
`Use clipform_upload_node_media with the public URL to attach to a node.`
|
|
23451
|
+
].filter(Boolean).join("\n")
|
|
23642
23452
|
);
|
|
23643
23453
|
}
|
|
23644
23454
|
);
|
|
@@ -23659,7 +23469,7 @@ function registerSearchMediaTool(server) {
|
|
|
23659
23469
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
23660
23470
|
},
|
|
23661
23471
|
async ({ query, kind, count }) => {
|
|
23662
|
-
const result = await
|
|
23472
|
+
const result = await callApi("/internal/search-media", {
|
|
23663
23473
|
body: { query, kind, count }
|
|
23664
23474
|
});
|
|
23665
23475
|
if (!result.ok) return errorResult(result.error);
|
|
@@ -23687,46 +23497,40 @@ function registerRenderCompositionTool(server) {
|
|
|
23687
23497
|
"clipform_render_composition",
|
|
23688
23498
|
{
|
|
23689
23499
|
title: "Render Composition",
|
|
23690
|
-
description: `Render a video composition to MP4 or PNG.
|
|
23500
|
+
description: `Render a video composition to MP4 or PNG. Blocks until complete and returns the public URL.
|
|
23691
23501
|
|
|
23692
23502
|
Output formats:
|
|
23693
23503
|
- mp4: Video file (H.264 codec, correct BT.709 colors, best for social media)
|
|
23694
23504
|
- png: Still image (single frame)
|
|
23695
23505
|
|
|
23696
|
-
For narrated quiz slideshows, prefer
|
|
23506
|
+
For narrated quiz slideshows, prefer clipform_generate_video which handles Ken Burns effects, audio sync, and storage upload. Use this tool for custom compositions like GlobeToCity, ScorecardQuiz, ShortFormQuiz, or PresenterDirected.`,
|
|
23697
23507
|
inputSchema: {
|
|
23698
|
-
compositionId: external_exports.string().describe("The composition ID (e.g. '
|
|
23508
|
+
compositionId: external_exports.string().describe("The composition ID (e.g. 'GlobeToCity', 'ScorecardQuiz', 'ShortFormQuiz')"),
|
|
23699
23509
|
outputFormat: external_exports.enum(["mp4", "png"]).default("mp4").describe("Output format (default: mp4)"),
|
|
23700
23510
|
inputProps: external_exports.record(external_exports.unknown()).optional().describe("Props object matching the composition's expected schema")
|
|
23701
23511
|
},
|
|
23702
23512
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
23703
23513
|
},
|
|
23704
23514
|
async ({ compositionId, outputFormat, inputProps }) => {
|
|
23705
|
-
const
|
|
23706
|
-
callInternalApi("/internal/render", {
|
|
23515
|
+
const result = await callApi("/internal/render", {
|
|
23707
23516
|
body: {
|
|
23708
23517
|
compositionId,
|
|
23709
23518
|
outputFormat,
|
|
23710
23519
|
inputProps: inputProps ?? {}
|
|
23711
23520
|
}
|
|
23712
|
-
}).then((result) => {
|
|
23713
|
-
if (!result.ok) {
|
|
23714
|
-
failJob(job.id, result.error);
|
|
23715
|
-
} else {
|
|
23716
|
-
completeJob(job.id, result.data);
|
|
23717
|
-
}
|
|
23718
|
-
}).catch((err) => {
|
|
23719
|
-
failJob(job.id, err instanceof Error ? err.message : String(err));
|
|
23720
23521
|
});
|
|
23522
|
+
if (!result.ok) return errorResult(result.error);
|
|
23523
|
+
const data = result.data;
|
|
23721
23524
|
return textResult(
|
|
23722
23525
|
[
|
|
23723
|
-
`Render
|
|
23526
|
+
`Render complete.`,
|
|
23724
23527
|
``,
|
|
23725
23528
|
`Composition: ${compositionId}`,
|
|
23726
|
-
`Format: ${outputFormat}`,
|
|
23727
|
-
`
|
|
23529
|
+
`Format: ${data.format || outputFormat}`,
|
|
23530
|
+
`Public URL: ${data.public_url}`,
|
|
23531
|
+
`Storage path: ${data.storage_path}`,
|
|
23728
23532
|
``,
|
|
23729
|
-
`
|
|
23533
|
+
`Use clipform_upload_node_media with the public URL to attach to a node.`
|
|
23730
23534
|
].join("\n")
|
|
23731
23535
|
);
|
|
23732
23536
|
}
|
|
@@ -23751,7 +23555,7 @@ function registerSearchMusicTool(server) {
|
|
|
23751
23555
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
23752
23556
|
},
|
|
23753
23557
|
async ({ query, count, instrumentalOnly, minDuration, maxDuration, tags }) => {
|
|
23754
|
-
const result = await
|
|
23558
|
+
const result = await callApi("/internal/search-music", {
|
|
23755
23559
|
body: { query, count, instrumentalOnly, minDuration, maxDuration, tags }
|
|
23756
23560
|
});
|
|
23757
23561
|
if (!result.ok) return errorResult(result.error);
|
|
@@ -23783,7 +23587,7 @@ function registerListCompositionsTool(server) {
|
|
|
23783
23587
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
23784
23588
|
},
|
|
23785
23589
|
async () => {
|
|
23786
|
-
const result = await
|
|
23590
|
+
const result = await callApi("/internal/compositions", {
|
|
23787
23591
|
method: "GET"
|
|
23788
23592
|
});
|
|
23789
23593
|
if (!result.ok) {
|
|
@@ -23797,14 +23601,11 @@ function registerListCompositionsTool(server) {
|
|
|
23797
23601
|
const lines = [`Available compositions (${compositions.length}):
|
|
23798
23602
|
`];
|
|
23799
23603
|
for (const comp of compositions) {
|
|
23800
|
-
|
|
23801
|
-
lines.push(
|
|
23802
|
-
lines.push(` Size: ${comp.width}x${comp.height}`);
|
|
23803
|
-
if (comp.defaultProps) {
|
|
23804
|
-
lines.push(` Default props: ${JSON.stringify(comp.defaultProps, null, 2)}`);
|
|
23805
|
-
}
|
|
23806
|
-
lines.push("");
|
|
23604
|
+
const duration3 = (comp.durationInFrames / comp.fps).toFixed(0);
|
|
23605
|
+
lines.push(`- **${comp.id}** \u2014 ${comp.width}x${comp.height}, ${duration3}s`);
|
|
23807
23606
|
}
|
|
23607
|
+
lines.push("");
|
|
23608
|
+
lines.push("Use clipform_render_composition with a compositionId to render. Pass inputProps matching the composition's schema.");
|
|
23808
23609
|
return textResult(lines.join("\n"));
|
|
23809
23610
|
}
|
|
23810
23611
|
);
|
|
@@ -23823,7 +23624,7 @@ function registerListAssetsTool(server) {
|
|
|
23823
23624
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
23824
23625
|
},
|
|
23825
23626
|
async ({ type }) => {
|
|
23826
|
-
const result = await
|
|
23627
|
+
const result = await callApi("/internal/assets", {
|
|
23827
23628
|
method: "GET",
|
|
23828
23629
|
params: { type: type ?? "all" }
|
|
23829
23630
|
});
|
|
@@ -23861,7 +23662,7 @@ function registerListAssetsTool(server) {
|
|
|
23861
23662
|
|
|
23862
23663
|
// src/tools/generate-video.ts
|
|
23863
23664
|
var slideshowStyleSchema2 = external_exports.object({
|
|
23864
|
-
preset: external_exports.enum(
|
|
23665
|
+
preset: external_exports.enum(KEN_BURNS_PRESETS).optional(),
|
|
23865
23666
|
zoom: external_exports.object({ from: external_exports.number().optional(), to: external_exports.number().optional() }).optional(),
|
|
23866
23667
|
pan: external_exports.object({ range: external_exports.number().optional(), scale: external_exports.number().optional() }).optional(),
|
|
23867
23668
|
zoomPan: external_exports.object({
|
|
@@ -23869,7 +23670,7 @@ var slideshowStyleSchema2 = external_exports.object({
|
|
|
23869
23670
|
to: external_exports.number().optional(),
|
|
23870
23671
|
rangeFraction: external_exports.number().optional()
|
|
23871
23672
|
}).optional(),
|
|
23872
|
-
easing: external_exports.enum(
|
|
23673
|
+
easing: external_exports.enum(KEN_BURNS_EASINGS).optional(),
|
|
23873
23674
|
blurPad: external_exports.object({
|
|
23874
23675
|
blurPx: external_exports.number().optional(),
|
|
23875
23676
|
brightness: external_exports.number().optional(),
|
|
@@ -23889,43 +23690,11 @@ function registerGenerateVideoTool(server) {
|
|
|
23889
23690
|
"clipform_generate_video",
|
|
23890
23691
|
{
|
|
23891
23692
|
title: "Generate Video",
|
|
23892
|
-
description: `Generate a video from images, video clips, or
|
|
23893
|
-
|
|
23894
|
-
## Workflow
|
|
23895
|
-
|
|
23896
|
-
1. Source media with clipform_search_media (kind: "image" or "video")
|
|
23897
|
-
2. Source audio with clipform_generate_tts (narration) or clipform_search_music (background)
|
|
23898
|
-
3. Call this tool with items + audio_url
|
|
23899
|
-
4. Attach the returned URL to a node via clipform_upload_node_media with media_type "video"
|
|
23900
|
-
|
|
23901
|
-
## Items
|
|
23902
|
-
|
|
23903
|
-
Each item is an image or video clip:
|
|
23904
|
-
- **type: "image"** - still image with Ken Burns motion (pan/zoom). Supports effect, fit, and style overrides.
|
|
23905
|
-
- **type: "video"** - video clip, cover-cropped to fill 9:16 frame. Audio muted by default (set volume: 1 to mix in). Use start_from to trim.
|
|
23906
|
-
|
|
23907
|
-
## Style presets (image items)
|
|
23908
|
-
|
|
23909
|
-
Pick one via default_style.preset (all images) or per-image style.preset:
|
|
23910
|
-
- **cinematic** (default) - smooth ease-in-out, subtle zoom/pan, subtle vignette
|
|
23911
|
-
- **dramatic** - big zoom, wide pan, ease-out, strong vignette. Reveals, climactic moments.
|
|
23912
|
-
- **calm** - minimal motion, linear easing, no vignette. Educational, reflective.
|
|
23913
|
-
- **documentary** - near-static, tiny pan, no vignette. Historical photos, talking-heads context.
|
|
23914
|
-
- **dreamy** - heavy blur-pad, soft vignette. Aspirational, nostalgic.
|
|
23915
|
-
- **moody** - desaturated, dark, strong vignette. Mystery, somber.
|
|
23916
|
-
|
|
23917
|
-
## Effects (image items)
|
|
23693
|
+
description: `Generate a video from images, video clips, or both, synced to audio. Creates 9:16 (720x1280) with Ken Burns effects and transitions. Blocks until complete. Returns a public URL.
|
|
23918
23694
|
|
|
23919
|
-
|
|
23920
|
-
Set per-image or use random_effects: true to shuffle.
|
|
23695
|
+
Workflow: 1) clipform_search_media (kind: "image" or "video") 2) clipform_generate_tts or clipform_search_music for audio 3) this tool 4) clipform_upload_node_media with media_type "video".
|
|
23921
23696
|
|
|
23922
|
-
|
|
23923
|
-
|
|
23924
|
-
fade (default), slide, wipe, none. Duration in seconds.
|
|
23925
|
-
|
|
23926
|
-
## Duration
|
|
23927
|
-
|
|
23928
|
-
Matches audio when audio_url is provided. Use duration_seconds for videos without audio.`,
|
|
23697
|
+
Items: type "image" (Ken Burns motion) or "video" (cover-cropped, muted by default). Style presets via default_style.preset: cinematic, dramatic, calm, documentary, dreamy, moody. Duration matches audio_url or set duration_seconds explicitly.`,
|
|
23929
23698
|
inputSchema: {
|
|
23930
23699
|
items: external_exports.array(
|
|
23931
23700
|
external_exports.object({
|
|
@@ -23933,7 +23702,7 @@ Matches audio when audio_url is provided. Use duration_seconds for videos withou
|
|
|
23933
23702
|
url: external_exports.string().url().describe("Media URL"),
|
|
23934
23703
|
effect: external_exports.string().optional().describe("Ken Burns effect (image only): zoom-in, zoom-out, pan-left, pan-right, pan-up, pan-down, zoom-in-pan-left, zoom-in-pan-right, random, static"),
|
|
23935
23704
|
aspect_ratio: external_exports.number().positive().optional().describe("Image width/height ratio (image only). Enables auto blur-pad for landscape images."),
|
|
23936
|
-
fit: external_exports.enum(
|
|
23705
|
+
fit: external_exports.enum(KEN_BURNS_FIT_MODES).optional().describe("Framing mode (image only). auto = cover for portrait, blur-pad for landscape."),
|
|
23937
23706
|
style: slideshowStyleSchema2.optional().describe("Creative style overrides (image only)"),
|
|
23938
23707
|
start_from: external_exports.number().min(0).optional().describe("Start time in seconds (video only). Trims the clip."),
|
|
23939
23708
|
volume: external_exports.number().min(0).max(1).optional().describe("Clip audio volume 0-1 (video only, default 0 = muted)")
|
|
@@ -23953,8 +23722,7 @@ Matches audio when audio_url is provided. Use duration_seconds for videos withou
|
|
|
23953
23722
|
},
|
|
23954
23723
|
async ({ items, audio_url, duration_seconds, random_effects, transition, default_style, background_color }) => {
|
|
23955
23724
|
const workspace_id = process.env.MCP_WORKSPACE_ID;
|
|
23956
|
-
const
|
|
23957
|
-
callInternalApi("/internal/generate-video", {
|
|
23725
|
+
const result = await callApi("/internal/generate-video", {
|
|
23958
23726
|
body: {
|
|
23959
23727
|
items,
|
|
23960
23728
|
audio_url,
|
|
@@ -23965,76 +23733,19 @@ Matches audio when audio_url is provided. Use duration_seconds for videos withou
|
|
|
23965
23733
|
background_color,
|
|
23966
23734
|
workspace_id
|
|
23967
23735
|
}
|
|
23968
|
-
}).then((result) => {
|
|
23969
|
-
if (!result.ok) {
|
|
23970
|
-
failJob(job.id, result.error);
|
|
23971
|
-
} else {
|
|
23972
|
-
completeJob(job.id, result.data);
|
|
23973
|
-
}
|
|
23974
|
-
}).catch((err) => {
|
|
23975
|
-
failJob(job.id, err instanceof Error ? err.message : String(err));
|
|
23976
23736
|
});
|
|
23737
|
+
if (!result.ok) return errorResult(result.error);
|
|
23738
|
+
const data = result.data;
|
|
23977
23739
|
return textResult(
|
|
23978
23740
|
[
|
|
23979
|
-
`
|
|
23980
|
-
``,
|
|
23981
|
-
`Job ID: ${job.id}`,
|
|
23982
|
-
``,
|
|
23983
|
-
`Renders typically take ${RENDER_TIMING.expectedRange}. Use clipform_check_render with this job ID to check status.`
|
|
23984
|
-
].join("\n")
|
|
23985
|
-
);
|
|
23986
|
-
}
|
|
23987
|
-
);
|
|
23988
|
-
}
|
|
23989
|
-
|
|
23990
|
-
// src/tools/check-render.ts
|
|
23991
|
-
function registerCheckRenderTool(server) {
|
|
23992
|
-
server.registerTool(
|
|
23993
|
-
"clipform_check_render",
|
|
23994
|
-
{
|
|
23995
|
-
title: "Check Render Status",
|
|
23996
|
-
description: `Check the status of a render job started by clipform_generate_video, clipform_generate_slideshow, or clipform_render_composition.
|
|
23997
|
-
|
|
23998
|
-
Returns the current status and, when complete, the output URL. If still rendering, wait ${RENDER_TIMING.pollDelay} before checking again.`,
|
|
23999
|
-
inputSchema: {
|
|
24000
|
-
job_id: external_exports.string().uuid().describe("The job ID returned by the render tool")
|
|
24001
|
-
},
|
|
24002
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
24003
|
-
},
|
|
24004
|
-
async ({ job_id }) => {
|
|
24005
|
-
pruneJobs();
|
|
24006
|
-
const job = getJob(job_id);
|
|
24007
|
-
if (!job) {
|
|
24008
|
-
return errorResult(`No render job found with ID ${job_id}. Jobs expire after 30 minutes.`);
|
|
24009
|
-
}
|
|
24010
|
-
if (job.status === "rendering") {
|
|
24011
|
-
const elapsed = Math.round((Date.now() - job.createdAt) / 1e3);
|
|
24012
|
-
return textResult(
|
|
24013
|
-
[
|
|
24014
|
-
`Status: rendering (${elapsed}s elapsed)`,
|
|
24015
|
-
`Tool: ${job.tool}`,
|
|
24016
|
-
``,
|
|
24017
|
-
`Still in progress. Check again in ${RENDER_TIMING.pollDelay}.`
|
|
24018
|
-
].join("\n")
|
|
24019
|
-
);
|
|
24020
|
-
}
|
|
24021
|
-
if (job.status === "failed") {
|
|
24022
|
-
return errorResult(`Render failed: ${job.error}`);
|
|
24023
|
-
}
|
|
24024
|
-
const data = job.result;
|
|
24025
|
-
return textResult(
|
|
24026
|
-
[
|
|
24027
|
-
`Status: complete`,
|
|
24028
|
-
`Tool: ${job.tool}`,
|
|
23741
|
+
`Video rendered (${items.length} item${items.length > 1 ? "s" : ""}).`,
|
|
24029
23742
|
``,
|
|
24030
|
-
|
|
24031
|
-
|
|
24032
|
-
|
|
24033
|
-
...data.outputPath ? [`Output: ${data.outputPath}`] : [],
|
|
24034
|
-
...data.format ? [`Format: ${data.format}`] : [],
|
|
23743
|
+
`Public URL: ${data.public_url}`,
|
|
23744
|
+
`Storage path: ${data.storage_path}`,
|
|
23745
|
+
data.duration_seconds ? `Duration: ${data.duration_seconds}s` : "",
|
|
24035
23746
|
``,
|
|
24036
|
-
`Use clipform_upload_node_media with the public URL to attach
|
|
24037
|
-
].join("\n")
|
|
23747
|
+
`Use clipform_upload_node_media with the public URL to attach to a node.`
|
|
23748
|
+
].filter(Boolean).join("\n")
|
|
24038
23749
|
);
|
|
24039
23750
|
}
|
|
24040
23751
|
);
|
|
@@ -24177,6 +23888,11 @@ async function getSessionContext() {
|
|
|
24177
23888
|
} else {
|
|
24178
23889
|
lines.push("Questions: unlimited");
|
|
24179
23890
|
}
|
|
23891
|
+
if (plan.form_limit !== null) {
|
|
23892
|
+
lines.push(`Form limit: ${plan.form_limit} forms`);
|
|
23893
|
+
} else {
|
|
23894
|
+
lines.push("Forms: unlimited");
|
|
23895
|
+
}
|
|
24180
23896
|
if (plan.custom_theme === false) {
|
|
24181
23897
|
lines.push("Custom themes: not available on this plan");
|
|
24182
23898
|
}
|
|
@@ -25057,7 +24773,6 @@ function createServer() {
|
|
|
25057
24773
|
registerGetNodeMediaTool(server);
|
|
25058
24774
|
registerDeleteNodeMediaTool(server);
|
|
25059
24775
|
registerSetNodeLogicTool(server);
|
|
25060
|
-
registerAttachNodeAudioTool(server);
|
|
25061
24776
|
registerLogGenerationTool(server);
|
|
25062
24777
|
registerSearchNewsTool(server);
|
|
25063
24778
|
registerYouTubeTranscriptTool(server);
|
|
@@ -25069,7 +24784,6 @@ function createServer() {
|
|
|
25069
24784
|
registerSearchMusicTool(server);
|
|
25070
24785
|
registerListCompositionsTool(server);
|
|
25071
24786
|
registerListAssetsTool(server);
|
|
25072
|
-
registerCheckRenderTool(server);
|
|
25073
24787
|
registerFetchBoundaryTool(server);
|
|
25074
24788
|
registerPrompts(server);
|
|
25075
24789
|
registerResources(server);
|
|
@@ -25078,7 +24792,6 @@ function createServer() {
|
|
|
25078
24792
|
|
|
25079
24793
|
export {
|
|
25080
24794
|
JSONRPCMessageSchema,
|
|
25081
|
-
setApiKey,
|
|
25082
24795
|
createServer
|
|
25083
24796
|
};
|
|
25084
|
-
//# sourceMappingURL=chunk-
|
|
24797
|
+
//# sourceMappingURL=chunk-MB6BZ2JN.js.map
|