@clipform/mcp-server 1.15.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-2SPXROLZ.js → chunk-MB6BZ2JN.js} +217 -463
- 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-2SPXROLZ.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
|
|
|
@@ -20918,6 +20917,7 @@ var NODE_TYPES = {
|
|
|
20918
20917
|
supports_media: false,
|
|
20919
20918
|
is_system: true,
|
|
20920
20919
|
is_active: true,
|
|
20920
|
+
show_in_results: false,
|
|
20921
20921
|
default_config: null,
|
|
20922
20922
|
config_schema: null,
|
|
20923
20923
|
response_schema: null,
|
|
@@ -20942,6 +20942,7 @@ var NODE_TYPES = {
|
|
|
20942
20942
|
supports_media: true,
|
|
20943
20943
|
is_system: false,
|
|
20944
20944
|
is_active: true,
|
|
20945
|
+
show_in_results: true,
|
|
20945
20946
|
default_config: {
|
|
20946
20947
|
selection_mode: "single",
|
|
20947
20948
|
choice: { enable_branching: true, record_scores: false },
|
|
@@ -21041,6 +21042,7 @@ var NODE_TYPES = {
|
|
|
21041
21042
|
supports_media: true,
|
|
21042
21043
|
is_system: false,
|
|
21043
21044
|
is_active: true,
|
|
21045
|
+
show_in_results: true,
|
|
21044
21046
|
default_config: {
|
|
21045
21047
|
formats: [
|
|
21046
21048
|
{ order: 0, format: "text" },
|
|
@@ -21083,7 +21085,17 @@ var NODE_TYPES = {
|
|
|
21083
21085
|
type: "object",
|
|
21084
21086
|
properties: {
|
|
21085
21087
|
type: { enum: ["audio", "video"], type: "string" },
|
|
21086
|
-
storage_path: { type: "string" }
|
|
21088
|
+
storage_path: { type: "string" },
|
|
21089
|
+
facing_mode: { enum: ["user", "environment"], type: "string" },
|
|
21090
|
+
preview_crop: {
|
|
21091
|
+
type: "object",
|
|
21092
|
+
properties: {
|
|
21093
|
+
stream_width: { type: "number" },
|
|
21094
|
+
stream_height: { type: "number" },
|
|
21095
|
+
preview_width: { type: "number" },
|
|
21096
|
+
preview_height: { type: "number" }
|
|
21097
|
+
}
|
|
21098
|
+
}
|
|
21087
21099
|
},
|
|
21088
21100
|
description: "Media response (when response_type is audio or video)"
|
|
21089
21101
|
},
|
|
@@ -21113,7 +21125,17 @@ var NODE_TYPES = {
|
|
|
21113
21125
|
type: "object",
|
|
21114
21126
|
properties: {
|
|
21115
21127
|
type: { enum: ["audio", "video"], type: "string" },
|
|
21116
|
-
storage_path: { type: "string" }
|
|
21128
|
+
storage_path: { type: "string" },
|
|
21129
|
+
facing_mode: { enum: ["user", "environment"], type: "string" },
|
|
21130
|
+
preview_crop: {
|
|
21131
|
+
type: "object",
|
|
21132
|
+
properties: {
|
|
21133
|
+
stream_width: { type: "number" },
|
|
21134
|
+
stream_height: { type: "number" },
|
|
21135
|
+
preview_width: { type: "number" },
|
|
21136
|
+
preview_height: { type: "number" }
|
|
21137
|
+
}
|
|
21138
|
+
}
|
|
21117
21139
|
}
|
|
21118
21140
|
},
|
|
21119
21141
|
transcription: {
|
|
@@ -21142,6 +21164,7 @@ var NODE_TYPES = {
|
|
|
21142
21164
|
supports_media: true,
|
|
21143
21165
|
is_system: false,
|
|
21144
21166
|
is_active: false,
|
|
21167
|
+
show_in_results: true,
|
|
21145
21168
|
default_config: null,
|
|
21146
21169
|
config_schema: {
|
|
21147
21170
|
type: "object",
|
|
@@ -21176,6 +21199,7 @@ var NODE_TYPES = {
|
|
|
21176
21199
|
supports_media: false,
|
|
21177
21200
|
is_system: false,
|
|
21178
21201
|
is_active: false,
|
|
21202
|
+
show_in_results: true,
|
|
21179
21203
|
default_config: null,
|
|
21180
21204
|
config_schema: {
|
|
21181
21205
|
type: "object",
|
|
@@ -21250,24 +21274,24 @@ var NODE_TYPES = {
|
|
|
21250
21274
|
supports_media: false,
|
|
21251
21275
|
is_system: false,
|
|
21252
21276
|
is_active: true,
|
|
21277
|
+
show_in_results: false,
|
|
21253
21278
|
default_config: {
|
|
21254
|
-
|
|
21255
|
-
|
|
21256
|
-
|
|
21257
|
-
|
|
21258
|
-
],
|
|
21259
|
-
prompt: ""
|
|
21260
|
-
},
|
|
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
|
+
],
|
|
21261
21283
|
consent_items: [
|
|
21262
|
-
{ id: "default-consent", label: "I agree to the privacy policy and terms of service", order: 0,
|
|
21284
|
+
{ id: "default-consent", name: "Privacy policy", label: "I agree to the privacy policy and terms of service", order: 0, type: "consent" }
|
|
21263
21285
|
]
|
|
21264
21286
|
},
|
|
21265
21287
|
config_schema: {
|
|
21266
21288
|
type: "object",
|
|
21289
|
+
required: ["fields"],
|
|
21267
21290
|
properties: {
|
|
21268
21291
|
title: { type: "string" },
|
|
21269
21292
|
fields: {
|
|
21270
21293
|
type: "array",
|
|
21294
|
+
minItems: 1,
|
|
21271
21295
|
items: {
|
|
21272
21296
|
type: "object",
|
|
21273
21297
|
required: ["id", "required"],
|
|
@@ -21288,9 +21312,10 @@ var NODE_TYPES = {
|
|
|
21288
21312
|
type: "object",
|
|
21289
21313
|
properties: {
|
|
21290
21314
|
id: { type: "string" },
|
|
21315
|
+
name: { type: "string" },
|
|
21291
21316
|
label: { type: "string" },
|
|
21292
21317
|
order: { type: "number" },
|
|
21293
|
-
|
|
21318
|
+
type: { type: "string", enum: ["consent", "opt_in"] }
|
|
21294
21319
|
}
|
|
21295
21320
|
}
|
|
21296
21321
|
}
|
|
@@ -21318,11 +21343,13 @@ var NODE_TYPES = {
|
|
|
21318
21343
|
type: "array",
|
|
21319
21344
|
items: {
|
|
21320
21345
|
type: "object",
|
|
21321
|
-
required: ["id", "label", "accepted"],
|
|
21346
|
+
required: ["id", "name", "label", "accepted", "type"],
|
|
21322
21347
|
properties: {
|
|
21323
21348
|
id: { type: "string" },
|
|
21349
|
+
name: { type: "string" },
|
|
21324
21350
|
label: { type: "string" },
|
|
21325
|
-
accepted: { type: "boolean" }
|
|
21351
|
+
accepted: { type: "boolean" },
|
|
21352
|
+
type: { type: "string", enum: ["consent", "opt_in"] }
|
|
21326
21353
|
}
|
|
21327
21354
|
},
|
|
21328
21355
|
description: "Consent checkbox entries"
|
|
@@ -21364,6 +21391,7 @@ var NODE_TYPES = {
|
|
|
21364
21391
|
supports_media: false,
|
|
21365
21392
|
is_system: false,
|
|
21366
21393
|
is_active: false,
|
|
21394
|
+
show_in_results: true,
|
|
21367
21395
|
default_config: null,
|
|
21368
21396
|
config_schema: {
|
|
21369
21397
|
type: "object",
|
|
@@ -21427,6 +21455,7 @@ var NODE_TYPES = {
|
|
|
21427
21455
|
supports_media: false,
|
|
21428
21456
|
is_system: false,
|
|
21429
21457
|
is_active: false,
|
|
21458
|
+
show_in_results: true,
|
|
21430
21459
|
default_config: { amount: null, currency: "usd", provider: "stripe" },
|
|
21431
21460
|
config_schema: {
|
|
21432
21461
|
type: "object",
|
|
@@ -21511,6 +21540,7 @@ var NODE_TYPES = {
|
|
|
21511
21540
|
supports_media: true,
|
|
21512
21541
|
is_system: false,
|
|
21513
21542
|
is_active: false,
|
|
21543
|
+
show_in_results: true,
|
|
21514
21544
|
default_config: {
|
|
21515
21545
|
choice: { enable_branching: true },
|
|
21516
21546
|
options: [
|
|
@@ -21573,6 +21603,7 @@ var NODE_TYPES = {
|
|
|
21573
21603
|
supports_media: true,
|
|
21574
21604
|
is_system: false,
|
|
21575
21605
|
is_active: true,
|
|
21606
|
+
show_in_results: false,
|
|
21576
21607
|
default_config: { options: [{ content: "Continue" }] },
|
|
21577
21608
|
config_schema: {
|
|
21578
21609
|
type: "object",
|
|
@@ -21611,6 +21642,7 @@ var NODE_TYPES = {
|
|
|
21611
21642
|
supports_media: false,
|
|
21612
21643
|
is_system: false,
|
|
21613
21644
|
is_active: true,
|
|
21645
|
+
show_in_results: false,
|
|
21614
21646
|
default_config: { url: "", auto_redirect: true },
|
|
21615
21647
|
config_schema: {
|
|
21616
21648
|
type: "object",
|
|
@@ -21641,6 +21673,7 @@ var NODE_TYPES = {
|
|
|
21641
21673
|
supports_media: false,
|
|
21642
21674
|
is_system: false,
|
|
21643
21675
|
is_active: false,
|
|
21676
|
+
show_in_results: false,
|
|
21644
21677
|
default_config: { links: [{ id: "default", url: "", title: "", description: "", order: 0 }], auto_redirect: false },
|
|
21645
21678
|
config_schema: {
|
|
21646
21679
|
type: "object",
|
|
@@ -21686,6 +21719,7 @@ var NODE_TYPES = {
|
|
|
21686
21719
|
supports_media: false,
|
|
21687
21720
|
is_system: false,
|
|
21688
21721
|
is_active: false,
|
|
21722
|
+
show_in_results: true,
|
|
21689
21723
|
default_config: null,
|
|
21690
21724
|
config_schema: {
|
|
21691
21725
|
type: "object",
|
|
@@ -21744,6 +21778,7 @@ var NODE_TYPES = {
|
|
|
21744
21778
|
supports_media: false,
|
|
21745
21779
|
is_system: false,
|
|
21746
21780
|
is_active: false,
|
|
21781
|
+
show_in_results: false,
|
|
21747
21782
|
default_config: { provider: "shopify", cta_mode: "checkout", source_mode: "manual" },
|
|
21748
21783
|
config_schema: {
|
|
21749
21784
|
type: "object",
|
|
@@ -21812,6 +21847,7 @@ var NODE_TYPES = {
|
|
|
21812
21847
|
supports_media: false,
|
|
21813
21848
|
is_system: false,
|
|
21814
21849
|
is_active: true,
|
|
21850
|
+
show_in_results: false,
|
|
21815
21851
|
default_config: null,
|
|
21816
21852
|
config_schema: {
|
|
21817
21853
|
type: "object",
|
|
@@ -21885,6 +21921,7 @@ var NODE_TYPE_METADATA = Object.fromEntries(
|
|
|
21885
21921
|
var TERMINAL_TYPES = Object.entries(NODE_TYPES).filter(([, v]) => v.is_terminal).map(([k]) => k);
|
|
21886
21922
|
var NON_COUNTABLE_TYPES = Object.entries(NODE_TYPES).filter(([, v]) => v.is_system || v.is_terminal).map(([k]) => k);
|
|
21887
21923
|
var NON_COUNTABLE_FILTER = `(${NON_COUNTABLE_TYPES.map((t) => `"${t}"`).join(",")})`;
|
|
21924
|
+
var RESULTS_EXCLUDED_TYPES = Object.entries(NODE_TYPES).filter(([, v]) => !v.show_in_results).map(([k]) => k);
|
|
21888
21925
|
|
|
21889
21926
|
// ../config/integration-catalog.js
|
|
21890
21927
|
var INTEGRATION_CATALOG = {
|
|
@@ -22101,16 +22138,21 @@ var CONTACT_FIELDS = [
|
|
|
22101
22138
|
{ id: "first_name", label: "First Name", type: "text", placeholder: "Enter first name", order: 1 },
|
|
22102
22139
|
{ id: "last_name", label: "Last Name", type: "text", placeholder: "Enter last name", order: 2 },
|
|
22103
22140
|
{ id: "email", label: "Email Address", type: "email", placeholder: "you@example.com", order: 3 },
|
|
22104
|
-
{ id: "phone", label: "Phone Number", type: "tel", placeholder: "(555) 123-4567", order: 4 }
|
|
22105
|
-
{ id: "company", label: "Company", type: "text", placeholder: "Company name", order: 5 },
|
|
22106
|
-
{ id: "job_title", label: "Job Title", type: "text", placeholder: "Your role", order: 6 },
|
|
22107
|
-
{ id: "website", label: "Website", type: "url", placeholder: "https://example.com", order: 7 },
|
|
22108
|
-
{ id: "linkedin", label: "LinkedIn Profile", type: "url", placeholder: "https://linkedin.com/in/username", order: 8 },
|
|
22109
|
-
{ 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 }
|
|
22110
22142
|
];
|
|
22111
22143
|
var CONTACT_FIELDS_MAP = Object.fromEntries(
|
|
22112
22144
|
CONTACT_FIELDS.map((f) => [f.id, f])
|
|
22113
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"];
|
|
22114
22156
|
|
|
22115
22157
|
// src/lib/schemas.ts
|
|
22116
22158
|
var ACTIVE_NODE_TYPES = Object.entries(NODE_TYPES).filter(([, def]) => def.is_active && !def.is_system).map(([type]) => type);
|
|
@@ -22159,8 +22201,15 @@ function generateConfigSummary(configSchema, typeKey) {
|
|
|
22159
22201
|
}
|
|
22160
22202
|
return parts.length > 0 ? `Config: ${parts.join(", ")}` : "";
|
|
22161
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
|
+
}
|
|
22162
22211
|
var NODE_TYPES_DESCRIPTION = (() => {
|
|
22163
|
-
const lines = ["Node types:"];
|
|
22212
|
+
const lines = ["Node types (omit config to use defaults where shown):"];
|
|
22164
22213
|
for (const type of ACTIVE_NODE_TYPES) {
|
|
22165
22214
|
const def = NODE_TYPES[type];
|
|
22166
22215
|
let line = `- ${type}: ${def.description || def.label}`;
|
|
@@ -22171,17 +22220,26 @@ var NODE_TYPES_DESCRIPTION = (() => {
|
|
|
22171
22220
|
if (configHint) {
|
|
22172
22221
|
line += `. ${configHint}`;
|
|
22173
22222
|
}
|
|
22223
|
+
const defaultStr = formatDefaultConfig(def.default_config);
|
|
22224
|
+
if (defaultStr) {
|
|
22225
|
+
line += `. Defaults: ${defaultStr}`;
|
|
22226
|
+
}
|
|
22174
22227
|
lines.push(line);
|
|
22175
22228
|
}
|
|
22176
22229
|
return lines.join("\n");
|
|
22177
22230
|
})();
|
|
22178
22231
|
var CONFIG_DESCRIPTION = (() => {
|
|
22179
|
-
const lines = ["Type-specific configuration. Per-type keys:"];
|
|
22232
|
+
const lines = ["Type-specific configuration. Omit to use defaults. Per-type keys:"];
|
|
22180
22233
|
for (const type of ACTIVE_NODE_TYPES) {
|
|
22181
22234
|
const def = NODE_TYPES[type];
|
|
22182
22235
|
const summary = generateConfigSummary(def.config_schema, type);
|
|
22183
22236
|
if (summary) {
|
|
22184
|
-
|
|
22237
|
+
let line = ` ${type}: ${summary}`;
|
|
22238
|
+
const defaultStr = formatDefaultConfig(def.default_config);
|
|
22239
|
+
if (defaultStr) {
|
|
22240
|
+
line += `. Defaults: ${defaultStr}`;
|
|
22241
|
+
}
|
|
22242
|
+
lines.push(line);
|
|
22185
22243
|
}
|
|
22186
22244
|
}
|
|
22187
22245
|
return lines.join("\n");
|
|
@@ -22214,83 +22272,44 @@ var NodeSchema = external_exports.object({
|
|
|
22214
22272
|
});
|
|
22215
22273
|
|
|
22216
22274
|
// src/lib/api-client.ts
|
|
22217
|
-
var
|
|
22218
|
-
|
|
22219
|
-
|
|
22220
|
-
|
|
22221
|
-
|
|
22222
|
-
function setApiKey(key) {
|
|
22223
|
-
_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
|
+
);
|
|
22224
22280
|
}
|
|
22225
|
-
|
|
22226
|
-
|
|
22227
|
-
|
|
22228
|
-
|
|
22229
|
-
|
|
22230
|
-
url += `?${searchParams.toString()}`;
|
|
22231
|
-
}
|
|
22232
|
-
const headers = {
|
|
22233
|
-
"Content-Type": "application/json"
|
|
22234
|
-
};
|
|
22235
|
-
const mcpAuth = getMcpAuth();
|
|
22236
|
-
if (mcpAuth && INTERNAL_SECRET) {
|
|
22237
|
-
headers["Authorization"] = `Bearer ${INTERNAL_SECRET}`;
|
|
22238
|
-
headers["X-Mcp-User"] = mcpAuth.user_id;
|
|
22239
|
-
headers["X-Mcp-Workspace"] = mcpAuth.workspace_id;
|
|
22240
|
-
if (mcpAuth.tool_name) headers["X-Mcp-Tool"] = mcpAuth.tool_name;
|
|
22241
|
-
} else if (_apiKey) {
|
|
22242
|
-
headers["Authorization"] = `Bearer ${_apiKey}`;
|
|
22243
|
-
}
|
|
22244
|
-
const fetchOptions = { method, headers };
|
|
22245
|
-
if (body && method !== "GET") {
|
|
22246
|
-
fetchOptions.body = JSON.stringify(body);
|
|
22247
|
-
}
|
|
22248
|
-
let response;
|
|
22249
|
-
try {
|
|
22250
|
-
response = await fetch(url, fetchOptions);
|
|
22251
|
-
} 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) {
|
|
22252
22286
|
return {
|
|
22253
|
-
|
|
22254
|
-
|
|
22255
|
-
|
|
22256
|
-
|
|
22257
|
-
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
|
|
22258
22290
|
};
|
|
22259
22291
|
}
|
|
22260
|
-
|
|
22261
|
-
|
|
22262
|
-
|
|
22263
|
-
const data = await response.json();
|
|
22264
|
-
if (!response.ok) {
|
|
22265
|
-
return {
|
|
22266
|
-
ok: false,
|
|
22267
|
-
status: response.status,
|
|
22268
|
-
error: data.error || `API error (${response.status})`
|
|
22269
|
-
};
|
|
22292
|
+
const apiKey = process.env.CLIPFORM_API_KEY;
|
|
22293
|
+
if (apiKey) {
|
|
22294
|
+
return { "Authorization": `Bearer ${apiKey}` };
|
|
22270
22295
|
}
|
|
22271
|
-
|
|
22296
|
+
throw new Error(
|
|
22297
|
+
"No API authentication configured. Set INTERNAL_SERVICE_SECRET + MCP_WORKSPACE_ID, or CLIPFORM_API_KEY."
|
|
22298
|
+
);
|
|
22272
22299
|
}
|
|
22273
|
-
async function
|
|
22274
|
-
const {
|
|
22275
|
-
|
|
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}`;
|
|
22276
22305
|
if (params) {
|
|
22277
22306
|
const searchParams = new URLSearchParams(params);
|
|
22278
22307
|
url += `?${searchParams.toString()}`;
|
|
22279
22308
|
}
|
|
22280
22309
|
const headers = {
|
|
22281
|
-
"Content-Type": "application/json"
|
|
22310
|
+
"Content-Type": "application/json",
|
|
22311
|
+
...getAuthHeaders()
|
|
22282
22312
|
};
|
|
22283
|
-
const mcpAuth = getMcpAuth();
|
|
22284
|
-
if (mcpAuth && INTERNAL_SECRET) {
|
|
22285
|
-
headers["Authorization"] = `Bearer ${INTERNAL_SECRET}`;
|
|
22286
|
-
headers["X-Mcp-User"] = mcpAuth.user_id;
|
|
22287
|
-
headers["X-Mcp-Workspace"] = mcpAuth.workspace_id;
|
|
22288
|
-
if (mcpAuth.tool_name) headers["X-Mcp-Tool"] = mcpAuth.tool_name;
|
|
22289
|
-
} else if (_apiKey) {
|
|
22290
|
-
headers["Authorization"] = `Bearer ${_apiKey}`;
|
|
22291
|
-
} else if (INTERNAL_SECRET) {
|
|
22292
|
-
headers["Authorization"] = `Bearer ${INTERNAL_SECRET}`;
|
|
22293
|
-
}
|
|
22294
22313
|
const fetchOptions = { method, headers };
|
|
22295
22314
|
if (body && method !== "GET") {
|
|
22296
22315
|
fetchOptions.body = JSON.stringify(body);
|
|
@@ -22302,7 +22321,7 @@ async function callInternalApi(path, options = {}) {
|
|
|
22302
22321
|
return {
|
|
22303
22322
|
ok: false,
|
|
22304
22323
|
status: 0,
|
|
22305
|
-
error: `Failed to connect to
|
|
22324
|
+
error: `Failed to connect to API at ${url}.
|
|
22306
22325
|
|
|
22307
22326
|
Error: ${err instanceof Error ? err.message : String(err)}`
|
|
22308
22327
|
};
|
|
@@ -22315,7 +22334,7 @@ Error: ${err instanceof Error ? err.message : String(err)}`
|
|
|
22315
22334
|
return {
|
|
22316
22335
|
ok: false,
|
|
22317
22336
|
status: response.status,
|
|
22318
|
-
error: data.error || `
|
|
22337
|
+
error: data.error || `API error (${response.status})`
|
|
22319
22338
|
};
|
|
22320
22339
|
}
|
|
22321
22340
|
return { ok: true, data };
|
|
@@ -22344,6 +22363,8 @@ ${NODE_TYPES_DESCRIPTION}
|
|
|
22344
22363
|
|
|
22345
22364
|
All type definitions and config schemas are derived from @vid-master/config (answer-types). Refer to the config descriptions above for the correct keys and shapes.
|
|
22346
22365
|
|
|
22366
|
+
Fields marked AI-PROTECTED must only be set when the user explicitly requests them. Do not invent values for font, branding, or embed settings.
|
|
22367
|
+
|
|
22347
22368
|
Example: A form that asks a question, collects contact info, then finishes:
|
|
22348
22369
|
{
|
|
22349
22370
|
title: "Quick Survey",
|
|
@@ -22360,8 +22381,8 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
22360
22381
|
disable_back_navigation: external_exports.boolean().optional().describe("Prevent going back. Set true for quizzes."),
|
|
22361
22382
|
primary_color: external_exports.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("Primary/brand color as 6-digit hex (e.g. '#FF5500'). Used for buttons and accents."),
|
|
22362
22383
|
background_color: external_exports.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("Background color as 6-digit hex (e.g. '#1A1A2E')."),
|
|
22363
|
-
font_family: external_exports.string().optional().describe("
|
|
22364
|
-
embed_autoplay: external_exports.boolean().optional().describe("
|
|
22384
|
+
font_family: external_exports.string().optional().describe("AI-PROTECTED: Only set when the user explicitly requests a specific font."),
|
|
22385
|
+
embed_autoplay: external_exports.boolean().optional().describe("AI-PROTECTED: Only set when the user explicitly requests autoplay. Default: false."),
|
|
22365
22386
|
tags: external_exports.array(external_exports.string()).optional().describe("Tags for indexing (e.g. ['quiz', 'trivia', 'arsenal']). Include format, genre, and topics.")
|
|
22366
22387
|
},
|
|
22367
22388
|
annotations: {
|
|
@@ -22389,14 +22410,27 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
22389
22410
|
workspace_id: workspaceId,
|
|
22390
22411
|
workspace_name: me.workspace?.name ?? null,
|
|
22391
22412
|
plan_name: me.plan?.name ?? "Free",
|
|
22392
|
-
node_limit: me.plan?.node_limit ?? null
|
|
22413
|
+
node_limit: me.plan?.node_limit ?? null,
|
|
22414
|
+
form_limit: me.plan?.form_limit ?? null
|
|
22393
22415
|
};
|
|
22394
|
-
const contentCount = nodes.filter((q) => q.type
|
|
22416
|
+
const contentCount = nodes.filter((q) => !NON_COUNTABLE_TYPES.includes(q.type)).length;
|
|
22395
22417
|
if (planContext.node_limit !== null && contentCount > planContext.node_limit) {
|
|
22396
22418
|
const upgradeUrl = `${BUSINESS.urls.dashboard}/billing`;
|
|
22397
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.`;
|
|
22398
22420
|
return errorResult(message);
|
|
22399
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
|
+
}
|
|
22400
22434
|
const createResult = await callApi("/forms", {
|
|
22401
22435
|
method: "POST",
|
|
22402
22436
|
body: { title, workspace_id: workspaceId }
|
|
@@ -22421,6 +22455,11 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
22421
22455
|
});
|
|
22422
22456
|
}
|
|
22423
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
|
+
}
|
|
22424
22463
|
const addResult = await callApi(`/forms/${formId}/nodes`, {
|
|
22425
22464
|
method: "POST",
|
|
22426
22465
|
body: { node: q }
|
|
@@ -22429,10 +22468,13 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
22429
22468
|
return errorResult(`Failed to add node "${q.prompt}": ${addResult.error}`);
|
|
22430
22469
|
}
|
|
22431
22470
|
}
|
|
22432
|
-
await callApi(`/forms/${formId}`, {
|
|
22471
|
+
const publishResult = await callApi(`/forms/${formId}`, {
|
|
22433
22472
|
method: "PATCH",
|
|
22434
|
-
body: {
|
|
22473
|
+
body: { is_live: true }
|
|
22435
22474
|
});
|
|
22475
|
+
if (!publishResult.ok) {
|
|
22476
|
+
return errorResult(`Form created but failed to publish: ${publishResult.error}. Form ID: ${formId}`);
|
|
22477
|
+
}
|
|
22436
22478
|
if (tags && tags.length > 0) {
|
|
22437
22479
|
await callApi(`/forms/${formId}/tags`, {
|
|
22438
22480
|
method: "PUT",
|
|
@@ -22457,7 +22499,9 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
22457
22499
|
`Pass form_id on follow-up tools (get_form, add_node, upload_node_media, etc.).`
|
|
22458
22500
|
);
|
|
22459
22501
|
if (planContext) {
|
|
22460
|
-
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}`;
|
|
22461
22505
|
if (planContext.auth_mode === "oauth") {
|
|
22462
22506
|
lines.push(
|
|
22463
22507
|
``,
|
|
@@ -22522,7 +22566,7 @@ function registerListFormsTool(server) {
|
|
|
22522
22566
|
const lines = [`Found ${data.forms.length} form(s):
|
|
22523
22567
|
`];
|
|
22524
22568
|
for (const f of data.forms) {
|
|
22525
|
-
const status = f.
|
|
22569
|
+
const status = f.is_live ? "live" : "draft";
|
|
22526
22570
|
const tagStr = f.tags.length > 0 ? ` [${f.tags.map((t) => t.name).join(", ")}]` : "";
|
|
22527
22571
|
lines.push(`- **${f.title || "(untitled)"}** [${status}]${tagStr}`);
|
|
22528
22572
|
lines.push(` ID: ${f.id}`);
|
|
@@ -22546,7 +22590,7 @@ function formatFormState(data) {
|
|
|
22546
22590
|
const lines = [
|
|
22547
22591
|
`Form: ${data.title}`,
|
|
22548
22592
|
`Form ID: ${data.form_id}`,
|
|
22549
|
-
`
|
|
22593
|
+
`Live: ${data.is_live}`,
|
|
22550
22594
|
``,
|
|
22551
22595
|
`Nodes (in order):`
|
|
22552
22596
|
];
|
|
@@ -22605,22 +22649,22 @@ function registerUpdateFormTool(server) {
|
|
|
22605
22649
|
"clipform_update_form",
|
|
22606
22650
|
{
|
|
22607
22651
|
title: "Update Clipform",
|
|
22608
|
-
description: `Update a form's title, publish status, settings, or tags. Use clipform_get_form first to see current values.`,
|
|
22652
|
+
description: `Update a form's title, publish status, settings, or tags. Use clipform_get_form first to see current values. Fields marked AI-PROTECTED must only be set when the user explicitly requests them.`,
|
|
22609
22653
|
inputSchema: {
|
|
22610
22654
|
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
22611
22655
|
title: external_exports.string().optional().describe("New form title"),
|
|
22612
|
-
|
|
22656
|
+
is_live: external_exports.boolean().optional().describe("AI-PROTECTED: Only change live state when the user explicitly asks. Do not auto-publish."),
|
|
22613
22657
|
show_step_counter: external_exports.boolean().optional().describe("Show step counter (e.g. '1/5'). Recommended for quizzes."),
|
|
22614
22658
|
disable_back_navigation: external_exports.boolean().optional().describe("Prevent respondents from going back. Recommended for quizzes."),
|
|
22615
22659
|
total_steps: external_exports.number().nullable().optional().describe("Override the total step count shown in the step counter. Set null to auto-calculate."),
|
|
22616
22660
|
primary_color: external_exports.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("Primary/brand color as 6-digit hex (e.g. '#FF5500'). Used for buttons and accents."),
|
|
22617
22661
|
background_color: external_exports.string().regex(/^#[0-9A-Fa-f]{6}$/).optional().describe("Background color as 6-digit hex (e.g. '#1A1A2E')."),
|
|
22618
|
-
font_family: external_exports.string().optional().describe("
|
|
22619
|
-
embed_autoplay: external_exports.boolean().optional().describe("
|
|
22662
|
+
font_family: external_exports.string().optional().describe("AI-PROTECTED: Only set when the user explicitly requests a specific font."),
|
|
22663
|
+
embed_autoplay: external_exports.boolean().optional().describe("AI-PROTECTED: Only set when the user explicitly requests autoplay. Default: false."),
|
|
22620
22664
|
description: external_exports.string().nullable().optional().describe("SEO description (meta description, og:description). Set null to clear."),
|
|
22621
|
-
author: external_exports.string().nullable().optional().describe("
|
|
22622
|
-
brand_name: external_exports.string().nullable().optional().describe("
|
|
22623
|
-
logo_url: external_exports.string().nullable().optional().describe("
|
|
22665
|
+
author: external_exports.string().nullable().optional().describe("AI-PROTECTED: Only set when the user explicitly provides an author name. Set null to clear."),
|
|
22666
|
+
brand_name: external_exports.string().nullable().optional().describe("AI-PROTECTED: Only set when the user explicitly provides a brand name. Set null to clear."),
|
|
22667
|
+
logo_url: external_exports.string().nullable().optional().describe("AI-PROTECTED: Only set when the user explicitly provides a logo URL. Set null to clear."),
|
|
22624
22668
|
tags: external_exports.array(external_exports.string()).optional().describe("Replace all tags on this form. Pass the full desired set (e.g. ['quiz', 'trivia', 'slug:elephants']). Omit to leave tags unchanged.")
|
|
22625
22669
|
},
|
|
22626
22670
|
annotations: {
|
|
@@ -22630,10 +22674,10 @@ function registerUpdateFormTool(server) {
|
|
|
22630
22674
|
openWorldHint: true
|
|
22631
22675
|
}
|
|
22632
22676
|
},
|
|
22633
|
-
async ({ form_id, title,
|
|
22677
|
+
async ({ form_id, title, is_live, show_step_counter, disable_back_navigation, total_steps, primary_color, background_color, font_family, embed_autoplay, description, author, brand_name, logo_url, tags }) => {
|
|
22634
22678
|
const body = {};
|
|
22635
22679
|
if (title !== void 0) body.title = title;
|
|
22636
|
-
if (
|
|
22680
|
+
if (is_live !== void 0) body.is_live = is_live;
|
|
22637
22681
|
if (show_step_counter !== void 0) body.show_step_counter = show_step_counter;
|
|
22638
22682
|
if (disable_back_navigation !== void 0) body.disable_back_navigation = disable_back_navigation;
|
|
22639
22683
|
if (total_steps !== void 0) body.total_steps = total_steps;
|
|
@@ -22665,8 +22709,8 @@ function registerUpdateFormTool(server) {
|
|
|
22665
22709
|
}
|
|
22666
22710
|
const updates = [];
|
|
22667
22711
|
if (title !== void 0) updates.push(`Title \u2192 "${title}"`);
|
|
22668
|
-
if (
|
|
22669
|
-
updates.push(`
|
|
22712
|
+
if (is_live !== void 0)
|
|
22713
|
+
updates.push(`Live \u2192 ${is_live}`);
|
|
22670
22714
|
if (show_step_counter !== void 0)
|
|
22671
22715
|
updates.push(`Step counter \u2192 ${show_step_counter}`);
|
|
22672
22716
|
if (disable_back_navigation !== void 0)
|
|
@@ -22738,11 +22782,7 @@ function registerAddNodeTool(server) {
|
|
|
22738
22782
|
"clipform_add_node",
|
|
22739
22783
|
{
|
|
22740
22784
|
title: "Add Node",
|
|
22741
|
-
description: `Add a new node to an existing form.
|
|
22742
|
-
|
|
22743
|
-
${NODE_TYPES_DESCRIPTION}
|
|
22744
|
-
|
|
22745
|
-
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.`,
|
|
22746
22786
|
inputSchema: {
|
|
22747
22787
|
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
22748
22788
|
node: NodeSchema.describe("The node to add"),
|
|
@@ -22891,6 +22931,7 @@ ${formState}` : confirmMsg);
|
|
|
22891
22931
|
}
|
|
22892
22932
|
|
|
22893
22933
|
// src/tools/upload-node-media.ts
|
|
22934
|
+
var MEDIA_SUPPORTED_TYPES = Object.entries(NODE_TYPES).filter(([, def]) => def.supports_media).map(([type]) => type);
|
|
22894
22935
|
var MediaItemSchema = external_exports.object({
|
|
22895
22936
|
node_id: external_exports.string().describe("The node ID"),
|
|
22896
22937
|
media_type: external_exports.enum(["video", "still"]).describe("Type of media"),
|
|
@@ -22919,7 +22960,7 @@ function registerUploadNodeMediaTool(server) {
|
|
|
22919
22960
|
title: "Upload Node Media",
|
|
22920
22961
|
description: `Upload media for one or more nodes. Pass one item or many (max 10). Multiple items upload sequentially.
|
|
22921
22962
|
|
|
22922
|
-
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.`,
|
|
22923
22964
|
inputSchema: {
|
|
22924
22965
|
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
22925
22966
|
items: external_exports.array(MediaItemSchema).min(1).max(10).describe("One or more media items to upload")
|
|
@@ -23108,44 +23149,6 @@ ${formState}` : confirmMsg);
|
|
|
23108
23149
|
);
|
|
23109
23150
|
}
|
|
23110
23151
|
|
|
23111
|
-
// src/tools/attach-node-audio.ts
|
|
23112
|
-
function registerAttachNodeAudioTool(server) {
|
|
23113
|
-
server.registerTool(
|
|
23114
|
-
"clipform_attach_audio",
|
|
23115
|
-
{
|
|
23116
|
-
title: "Attach Audio to Node",
|
|
23117
|
-
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).`,
|
|
23118
|
-
inputSchema: {
|
|
23119
|
-
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
23120
|
-
node_id: external_exports.string().describe("The node ID (must already have media attached)"),
|
|
23121
|
-
url: external_exports.string().url().describe("Public URL to the audio file (WAV, MP3, or OGG)")
|
|
23122
|
-
},
|
|
23123
|
-
annotations: {
|
|
23124
|
-
readOnlyHint: false,
|
|
23125
|
-
destructiveHint: false,
|
|
23126
|
-
idempotentHint: true,
|
|
23127
|
-
openWorldHint: true
|
|
23128
|
-
}
|
|
23129
|
-
},
|
|
23130
|
-
async ({ form_id, node_id, url }) => {
|
|
23131
|
-
const result = await callApi(
|
|
23132
|
-
`/forms/${form_id}/nodes/${node_id}/audio`,
|
|
23133
|
-
{
|
|
23134
|
-
method: "PUT",
|
|
23135
|
-
body: { url }
|
|
23136
|
-
}
|
|
23137
|
-
);
|
|
23138
|
-
if (!result.ok) {
|
|
23139
|
-
return errorResult(result.error);
|
|
23140
|
-
}
|
|
23141
|
-
return textResult(
|
|
23142
|
-
`Audio attached to node ${node_id}.
|
|
23143
|
-
Audio path: ${result.data.audio_storage_path}`
|
|
23144
|
-
);
|
|
23145
|
-
}
|
|
23146
|
-
);
|
|
23147
|
-
}
|
|
23148
|
-
|
|
23149
23152
|
// src/tools/log-generation.ts
|
|
23150
23153
|
function registerLogGenerationTool(server) {
|
|
23151
23154
|
server.registerTool(
|
|
@@ -23172,7 +23175,7 @@ function registerLogGenerationTool(server) {
|
|
|
23172
23175
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
23173
23176
|
},
|
|
23174
23177
|
async ({ form_id, summary, details }) => {
|
|
23175
|
-
const result = await
|
|
23178
|
+
const result = await callApi("/internal/log-generation", {
|
|
23176
23179
|
body: { form_id, summary, details }
|
|
23177
23180
|
});
|
|
23178
23181
|
if (!result.ok) return errorResult(result.error);
|
|
@@ -23205,7 +23208,7 @@ If neither native web search nor this tool is available and the topic is post-cu
|
|
|
23205
23208
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
23206
23209
|
},
|
|
23207
23210
|
async ({ query, count }) => {
|
|
23208
|
-
const result = await
|
|
23211
|
+
const result = await callApi("/internal/search-news", {
|
|
23209
23212
|
body: { query, count }
|
|
23210
23213
|
});
|
|
23211
23214
|
if (!result.ok) return errorResult(result.error);
|
|
@@ -23264,7 +23267,7 @@ DOES NOT WORK FOR:
|
|
|
23264
23267
|
}
|
|
23265
23268
|
},
|
|
23266
23269
|
async ({ url, lang, max_chars }) => {
|
|
23267
|
-
const result = await
|
|
23270
|
+
const result = await callApi("/internal/youtube-transcript", {
|
|
23268
23271
|
body: { url, lang, max_chars }
|
|
23269
23272
|
});
|
|
23270
23273
|
if (!result.ok) return errorResult(result.error);
|
|
@@ -23316,7 +23319,7 @@ Returns audio URL and word-level captions per item.`,
|
|
|
23316
23319
|
const workspace_id = process.env.MCP_WORKSPACE_ID;
|
|
23317
23320
|
const results = await Promise.allSettled(
|
|
23318
23321
|
items.map(
|
|
23319
|
-
(item) =>
|
|
23322
|
+
(item) => callApi("/internal/tts", {
|
|
23320
23323
|
body: { text: item.text, voice: item.voice, workspace_id }
|
|
23321
23324
|
})
|
|
23322
23325
|
)
|
|
@@ -23354,101 +23357,9 @@ Returns audio URL and word-level captions per item.`,
|
|
|
23354
23357
|
);
|
|
23355
23358
|
}
|
|
23356
23359
|
|
|
23357
|
-
// ../../node_modules/uuid/dist/esm/stringify.js
|
|
23358
|
-
var byteToHex = [];
|
|
23359
|
-
for (let i = 0; i < 256; ++i) {
|
|
23360
|
-
byteToHex.push((i + 256).toString(16).slice(1));
|
|
23361
|
-
}
|
|
23362
|
-
function unsafeStringify(arr, offset = 0) {
|
|
23363
|
-
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();
|
|
23364
|
-
}
|
|
23365
|
-
|
|
23366
|
-
// ../../node_modules/uuid/dist/esm/rng.js
|
|
23367
|
-
import { randomFillSync } from "crypto";
|
|
23368
|
-
var rnds8Pool = new Uint8Array(256);
|
|
23369
|
-
var poolPtr = rnds8Pool.length;
|
|
23370
|
-
function rng() {
|
|
23371
|
-
if (poolPtr > rnds8Pool.length - 16) {
|
|
23372
|
-
randomFillSync(rnds8Pool);
|
|
23373
|
-
poolPtr = 0;
|
|
23374
|
-
}
|
|
23375
|
-
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
|
23376
|
-
}
|
|
23377
|
-
|
|
23378
|
-
// ../../node_modules/uuid/dist/esm/native.js
|
|
23379
|
-
import { randomUUID } from "crypto";
|
|
23380
|
-
var native_default = { randomUUID };
|
|
23381
|
-
|
|
23382
|
-
// ../../node_modules/uuid/dist/esm/v4.js
|
|
23383
|
-
function v4(options, buf, offset) {
|
|
23384
|
-
if (native_default.randomUUID && !buf && !options) {
|
|
23385
|
-
return native_default.randomUUID();
|
|
23386
|
-
}
|
|
23387
|
-
options = options || {};
|
|
23388
|
-
const rnds = options.random ?? options.rng?.() ?? rng();
|
|
23389
|
-
if (rnds.length < 16) {
|
|
23390
|
-
throw new Error("Random bytes length must be >= 16");
|
|
23391
|
-
}
|
|
23392
|
-
rnds[6] = rnds[6] & 15 | 64;
|
|
23393
|
-
rnds[8] = rnds[8] & 63 | 128;
|
|
23394
|
-
if (buf) {
|
|
23395
|
-
offset = offset || 0;
|
|
23396
|
-
if (offset < 0 || offset + 16 > buf.length) {
|
|
23397
|
-
throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
|
|
23398
|
-
}
|
|
23399
|
-
for (let i = 0; i < 16; ++i) {
|
|
23400
|
-
buf[offset + i] = rnds[i];
|
|
23401
|
-
}
|
|
23402
|
-
return buf;
|
|
23403
|
-
}
|
|
23404
|
-
return unsafeStringify(rnds);
|
|
23405
|
-
}
|
|
23406
|
-
var v4_default = v4;
|
|
23407
|
-
|
|
23408
|
-
// src/lib/render-jobs.ts
|
|
23409
|
-
var jobs = /* @__PURE__ */ new Map();
|
|
23410
|
-
var MAX_AGE_MS = 30 * 60 * 1e3;
|
|
23411
|
-
var RENDER_TIMING = {
|
|
23412
|
-
expectedRange: "15-120 seconds",
|
|
23413
|
-
pollDelay: "~15 seconds"
|
|
23414
|
-
};
|
|
23415
|
-
function createJob(tool) {
|
|
23416
|
-
const job = {
|
|
23417
|
-
id: v4_default(),
|
|
23418
|
-
status: "rendering",
|
|
23419
|
-
tool,
|
|
23420
|
-
createdAt: Date.now()
|
|
23421
|
-
};
|
|
23422
|
-
jobs.set(job.id, job);
|
|
23423
|
-
return job;
|
|
23424
|
-
}
|
|
23425
|
-
function completeJob(id, result) {
|
|
23426
|
-
const job = jobs.get(id);
|
|
23427
|
-
if (job) {
|
|
23428
|
-
job.status = "complete";
|
|
23429
|
-
job.result = result;
|
|
23430
|
-
}
|
|
23431
|
-
}
|
|
23432
|
-
function failJob(id, error2) {
|
|
23433
|
-
const job = jobs.get(id);
|
|
23434
|
-
if (job) {
|
|
23435
|
-
job.status = "failed";
|
|
23436
|
-
job.error = error2;
|
|
23437
|
-
}
|
|
23438
|
-
}
|
|
23439
|
-
function getJob(id) {
|
|
23440
|
-
return jobs.get(id);
|
|
23441
|
-
}
|
|
23442
|
-
function pruneJobs() {
|
|
23443
|
-
const cutoff = Date.now() - MAX_AGE_MS;
|
|
23444
|
-
for (const [id, job] of jobs) {
|
|
23445
|
-
if (job.createdAt < cutoff) jobs.delete(id);
|
|
23446
|
-
}
|
|
23447
|
-
}
|
|
23448
|
-
|
|
23449
23360
|
// src/tools/generate-slideshow.ts
|
|
23450
23361
|
var slideshowStyleSchema = external_exports.object({
|
|
23451
|
-
preset: external_exports.enum(
|
|
23362
|
+
preset: external_exports.enum(KEN_BURNS_PRESETS).optional().describe("Style preset: cinematic (default), dramatic, calm, documentary, dreamy, moody."),
|
|
23452
23363
|
zoom: external_exports.object({
|
|
23453
23364
|
from: external_exports.number().optional().describe("Starting scale for zoom-in / ending scale for zoom-out. Default 1.0."),
|
|
23454
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).")
|
|
@@ -23462,7 +23373,7 @@ var slideshowStyleSchema = external_exports.object({
|
|
|
23462
23373
|
to: external_exports.number().optional(),
|
|
23463
23374
|
rangeFraction: external_exports.number().optional().describe("Multiplier on pan.range for zoom-in-pan-* effects. Default 0.5.")
|
|
23464
23375
|
}).optional(),
|
|
23465
|
-
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."),
|
|
23466
23377
|
blurPad: external_exports.object({
|
|
23467
23378
|
blurPx: external_exports.number().optional().describe("Blur strength on the background layer. Default 40. 60+ for heavy dreamy effect, 20 for just-barely."),
|
|
23468
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."),
|
|
@@ -23485,75 +23396,20 @@ function registerGenerateSlideshowTool(server) {
|
|
|
23485
23396
|
"clipform_generate_slideshow",
|
|
23486
23397
|
{
|
|
23487
23398
|
title: "Generate Slideshow",
|
|
23488
|
-
description:
|
|
23489
|
-
|
|
23490
|
-
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.
|
|
23491
|
-
|
|
23492
|
-
You are the director. Every stylistic choice below is yours - defaults exist for convenience but override anything that fits your creative vision.
|
|
23399
|
+
description: `Deprecated: prefer clipform_generate_video which supports both images and video clips.
|
|
23493
23400
|
|
|
23494
|
-
|
|
23495
|
-
|
|
23496
|
-
1. Source images (use clipform_search_media with kind: "image"). Prefer portrait sources for 9:16 output; landscape works too via blur-pad fallback.
|
|
23497
|
-
2. Produce narration audio (clipform_generate_tts).
|
|
23498
|
-
3. Call this tool with images + audio_url + your creative direction.
|
|
23499
|
-
4. Attach the returned public URL to a node via clipform_upload_media with media_type "video".
|
|
23500
|
-
|
|
23501
|
-
## Image framing (per-image)
|
|
23502
|
-
|
|
23503
|
-
- **aspect_ratio** (width/height): pass through from the image source so the composition can auto-pick framing. Landscape images with no aspect_ratio will cover-crop and may pixelate.
|
|
23504
|
-
- **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).
|
|
23505
|
-
- **style**: per-image creative overrides (see Style Knobs below).
|
|
23506
|
-
|
|
23507
|
-
## Effects
|
|
23508
|
-
|
|
23509
|
-
zoom-in, zoom-out, pan-left, pan-right, pan-up, pan-down, zoom-in-pan-left, zoom-in-pan-right, random, static. Set 'random' or pass random_effects: true to shuffle per image.
|
|
23510
|
-
|
|
23511
|
-
## Transitions
|
|
23512
|
-
|
|
23513
|
-
fade (default), slide, wipe. Legacy names (fadeblack, slideleft, etc.) also work. duration is in seconds.
|
|
23514
|
-
|
|
23515
|
-
## Style presets (fastest path)
|
|
23516
|
-
|
|
23517
|
-
Pick one as a starting point via \`style.preset\` (per-image) or \`default_style.preset\` (global). Override individual knobs on top.
|
|
23518
|
-
|
|
23519
|
-
- **cinematic** - the default feel. Smooth ease-in-out, zoom 1.0\u21921.3, subtle pan, subtle vignette.
|
|
23520
|
-
- **dramatic** - zoom 1.0\u21921.4, ease-out, wider pan (0.07), strong vignette. Good for big-reveal content, competitions, climactic moments.
|
|
23521
|
-
- **calm** - zoom 1.0\u21921.08, linear easing, no vignette. Reflective topics, wellness, educational explainers.
|
|
23522
|
-
- **documentary** - near-static (zoom 1.0\u21921.05, tiny pan, no vignette). Talking-heads-style context shots, historical photos.
|
|
23523
|
-
- **dreamy** - heavy blur-pad (blurPx 60, brightness 0.75), soft vignette. Aspirational, romantic, nostalgic.
|
|
23524
|
-
- **moody** - desaturated blur-pad (brightness 0.35, saturate 0.7), strong vignette, dark wrapper color. Crime, mystery, somber topics.
|
|
23525
|
-
|
|
23526
|
-
Per-image preset wins over default_style preset. Per-field overrides (zoom, pan, etc.) merge on top of whichever preset is active.
|
|
23401
|
+
Generate a Ken Burns slideshow video (9:16, 720x1280) from images + audio. Blocks until complete. Returns a public URL.
|
|
23527
23402
|
|
|
23528
|
-
|
|
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".
|
|
23529
23404
|
|
|
23530
|
-
|
|
23531
|
-
- **pan**: { range, scale } - drift magnitude. 0.03 subtle, 0.05 default, 0.08+ sweeping.
|
|
23532
|
-
- **easing**: 'ease-in-out' (default, cinematic) | 'ease-in' (accelerate) | 'ease-out' (decelerate, good for reveals) | 'linear' (mechanical).
|
|
23533
|
-
- **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.
|
|
23534
|
-
- **vignette**: false to disable, or { opacity, innerRadiusPercent }. Stronger vignette (opacity 0.6) for dramatic focus; disable for bright, flat looks.
|
|
23535
|
-
- **backgroundColor**: hex color shown behind the image frame (default '#000').
|
|
23536
|
-
- **autoBlurPadThreshold**: when fit='auto', images wider than this ratio get blur-pad. Default 1.1.
|
|
23537
|
-
|
|
23538
|
-
## Global options
|
|
23539
|
-
|
|
23540
|
-
- **default_style**: style applied to every image unless the image overrides per-field. Per-image style merges over default_style at the field level.
|
|
23541
|
-
- **background_color**: viewport wrapper color, visible during cross-fades.
|
|
23542
|
-
- **random_effects**: shuffles effects across images when true.
|
|
23543
|
-
|
|
23544
|
-
## Creative guidance
|
|
23545
|
-
|
|
23546
|
-
- 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.
|
|
23547
|
-
- Quiet/reflective topics: slow easing ('linear'), tight zoom (1.0\u21921.08), no vignette.
|
|
23548
|
-
- Energetic topics: punchy zoom (1.0\u21921.35), ease-out, wider pan.
|
|
23549
|
-
- 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.`,
|
|
23550
23406
|
inputSchema: {
|
|
23551
23407
|
images: external_exports.array(
|
|
23552
23408
|
external_exports.object({
|
|
23553
23409
|
url: external_exports.string().url().describe("Image URL"),
|
|
23554
23410
|
effect: external_exports.string().optional().describe("Ken Burns effect name (see description). Pass 'random' to shuffle this image only."),
|
|
23555
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."),
|
|
23556
|
-
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."),
|
|
23557
23413
|
style: slideshowStyleSchema.optional()
|
|
23558
23414
|
})
|
|
23559
23415
|
).min(1).max(20).describe("Images for the slideshow (1-20)"),
|
|
@@ -23570,8 +23426,7 @@ Per-image preset wins over default_style preset. Per-field overrides (zoom, pan,
|
|
|
23570
23426
|
},
|
|
23571
23427
|
async ({ images, audio_url, random_effects, transition, default_style, background_color }) => {
|
|
23572
23428
|
const workspace_id = process.env.MCP_WORKSPACE_ID;
|
|
23573
|
-
const
|
|
23574
|
-
callInternalApi("/internal/slideshow", {
|
|
23429
|
+
const result = await callApi("/internal/slideshow", {
|
|
23575
23430
|
body: {
|
|
23576
23431
|
images,
|
|
23577
23432
|
audio_url,
|
|
@@ -23581,23 +23436,19 @@ Per-image preset wins over default_style preset. Per-field overrides (zoom, pan,
|
|
|
23581
23436
|
background_color,
|
|
23582
23437
|
workspace_id
|
|
23583
23438
|
}
|
|
23584
|
-
}).then((result) => {
|
|
23585
|
-
if (!result.ok) {
|
|
23586
|
-
failJob(job.id, result.error);
|
|
23587
|
-
} else {
|
|
23588
|
-
completeJob(job.id, result.data);
|
|
23589
|
-
}
|
|
23590
|
-
}).catch((err) => {
|
|
23591
|
-
failJob(job.id, err instanceof Error ? err.message : String(err));
|
|
23592
23439
|
});
|
|
23440
|
+
if (!result.ok) return errorResult(result.error);
|
|
23441
|
+
const data = result.data;
|
|
23593
23442
|
return textResult(
|
|
23594
23443
|
[
|
|
23595
|
-
`Slideshow
|
|
23444
|
+
`Slideshow rendered (${images.length} image${images.length > 1 ? "s" : ""}).`,
|
|
23596
23445
|
``,
|
|
23597
|
-
`
|
|
23446
|
+
`Public URL: ${data.public_url}`,
|
|
23447
|
+
`Storage path: ${data.storage_path}`,
|
|
23448
|
+
data.duration_seconds ? `Duration: ${data.duration_seconds}s` : "",
|
|
23598
23449
|
``,
|
|
23599
|
-
`
|
|
23600
|
-
].join("\n")
|
|
23450
|
+
`Use clipform_upload_node_media with the public URL to attach to a node.`
|
|
23451
|
+
].filter(Boolean).join("\n")
|
|
23601
23452
|
);
|
|
23602
23453
|
}
|
|
23603
23454
|
);
|
|
@@ -23618,7 +23469,7 @@ function registerSearchMediaTool(server) {
|
|
|
23618
23469
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
23619
23470
|
},
|
|
23620
23471
|
async ({ query, kind, count }) => {
|
|
23621
|
-
const result = await
|
|
23472
|
+
const result = await callApi("/internal/search-media", {
|
|
23622
23473
|
body: { query, kind, count }
|
|
23623
23474
|
});
|
|
23624
23475
|
if (!result.ok) return errorResult(result.error);
|
|
@@ -23646,46 +23497,40 @@ function registerRenderCompositionTool(server) {
|
|
|
23646
23497
|
"clipform_render_composition",
|
|
23647
23498
|
{
|
|
23648
23499
|
title: "Render Composition",
|
|
23649
|
-
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.
|
|
23650
23501
|
|
|
23651
23502
|
Output formats:
|
|
23652
23503
|
- mp4: Video file (H.264 codec, correct BT.709 colors, best for social media)
|
|
23653
23504
|
- png: Still image (single frame)
|
|
23654
23505
|
|
|
23655
|
-
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.`,
|
|
23656
23507
|
inputSchema: {
|
|
23657
|
-
compositionId: external_exports.string().describe("The composition ID (e.g. '
|
|
23508
|
+
compositionId: external_exports.string().describe("The composition ID (e.g. 'GlobeToCity', 'ScorecardQuiz', 'ShortFormQuiz')"),
|
|
23658
23509
|
outputFormat: external_exports.enum(["mp4", "png"]).default("mp4").describe("Output format (default: mp4)"),
|
|
23659
23510
|
inputProps: external_exports.record(external_exports.unknown()).optional().describe("Props object matching the composition's expected schema")
|
|
23660
23511
|
},
|
|
23661
23512
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
23662
23513
|
},
|
|
23663
23514
|
async ({ compositionId, outputFormat, inputProps }) => {
|
|
23664
|
-
const
|
|
23665
|
-
callInternalApi("/internal/render", {
|
|
23515
|
+
const result = await callApi("/internal/render", {
|
|
23666
23516
|
body: {
|
|
23667
23517
|
compositionId,
|
|
23668
23518
|
outputFormat,
|
|
23669
23519
|
inputProps: inputProps ?? {}
|
|
23670
23520
|
}
|
|
23671
|
-
}).then((result) => {
|
|
23672
|
-
if (!result.ok) {
|
|
23673
|
-
failJob(job.id, result.error);
|
|
23674
|
-
} else {
|
|
23675
|
-
completeJob(job.id, result.data);
|
|
23676
|
-
}
|
|
23677
|
-
}).catch((err) => {
|
|
23678
|
-
failJob(job.id, err instanceof Error ? err.message : String(err));
|
|
23679
23521
|
});
|
|
23522
|
+
if (!result.ok) return errorResult(result.error);
|
|
23523
|
+
const data = result.data;
|
|
23680
23524
|
return textResult(
|
|
23681
23525
|
[
|
|
23682
|
-
`Render
|
|
23526
|
+
`Render complete.`,
|
|
23683
23527
|
``,
|
|
23684
23528
|
`Composition: ${compositionId}`,
|
|
23685
|
-
`Format: ${outputFormat}`,
|
|
23686
|
-
`
|
|
23529
|
+
`Format: ${data.format || outputFormat}`,
|
|
23530
|
+
`Public URL: ${data.public_url}`,
|
|
23531
|
+
`Storage path: ${data.storage_path}`,
|
|
23687
23532
|
``,
|
|
23688
|
-
`
|
|
23533
|
+
`Use clipform_upload_node_media with the public URL to attach to a node.`
|
|
23689
23534
|
].join("\n")
|
|
23690
23535
|
);
|
|
23691
23536
|
}
|
|
@@ -23710,7 +23555,7 @@ function registerSearchMusicTool(server) {
|
|
|
23710
23555
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true }
|
|
23711
23556
|
},
|
|
23712
23557
|
async ({ query, count, instrumentalOnly, minDuration, maxDuration, tags }) => {
|
|
23713
|
-
const result = await
|
|
23558
|
+
const result = await callApi("/internal/search-music", {
|
|
23714
23559
|
body: { query, count, instrumentalOnly, minDuration, maxDuration, tags }
|
|
23715
23560
|
});
|
|
23716
23561
|
if (!result.ok) return errorResult(result.error);
|
|
@@ -23742,7 +23587,7 @@ function registerListCompositionsTool(server) {
|
|
|
23742
23587
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
23743
23588
|
},
|
|
23744
23589
|
async () => {
|
|
23745
|
-
const result = await
|
|
23590
|
+
const result = await callApi("/internal/compositions", {
|
|
23746
23591
|
method: "GET"
|
|
23747
23592
|
});
|
|
23748
23593
|
if (!result.ok) {
|
|
@@ -23756,14 +23601,11 @@ function registerListCompositionsTool(server) {
|
|
|
23756
23601
|
const lines = [`Available compositions (${compositions.length}):
|
|
23757
23602
|
`];
|
|
23758
23603
|
for (const comp of compositions) {
|
|
23759
|
-
|
|
23760
|
-
lines.push(
|
|
23761
|
-
lines.push(` Size: ${comp.width}x${comp.height}`);
|
|
23762
|
-
if (comp.defaultProps) {
|
|
23763
|
-
lines.push(` Default props: ${JSON.stringify(comp.defaultProps, null, 2)}`);
|
|
23764
|
-
}
|
|
23765
|
-
lines.push("");
|
|
23604
|
+
const duration3 = (comp.durationInFrames / comp.fps).toFixed(0);
|
|
23605
|
+
lines.push(`- **${comp.id}** \u2014 ${comp.width}x${comp.height}, ${duration3}s`);
|
|
23766
23606
|
}
|
|
23607
|
+
lines.push("");
|
|
23608
|
+
lines.push("Use clipform_render_composition with a compositionId to render. Pass inputProps matching the composition's schema.");
|
|
23767
23609
|
return textResult(lines.join("\n"));
|
|
23768
23610
|
}
|
|
23769
23611
|
);
|
|
@@ -23782,7 +23624,7 @@ function registerListAssetsTool(server) {
|
|
|
23782
23624
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
23783
23625
|
},
|
|
23784
23626
|
async ({ type }) => {
|
|
23785
|
-
const result = await
|
|
23627
|
+
const result = await callApi("/internal/assets", {
|
|
23786
23628
|
method: "GET",
|
|
23787
23629
|
params: { type: type ?? "all" }
|
|
23788
23630
|
});
|
|
@@ -23820,7 +23662,7 @@ function registerListAssetsTool(server) {
|
|
|
23820
23662
|
|
|
23821
23663
|
// src/tools/generate-video.ts
|
|
23822
23664
|
var slideshowStyleSchema2 = external_exports.object({
|
|
23823
|
-
preset: external_exports.enum(
|
|
23665
|
+
preset: external_exports.enum(KEN_BURNS_PRESETS).optional(),
|
|
23824
23666
|
zoom: external_exports.object({ from: external_exports.number().optional(), to: external_exports.number().optional() }).optional(),
|
|
23825
23667
|
pan: external_exports.object({ range: external_exports.number().optional(), scale: external_exports.number().optional() }).optional(),
|
|
23826
23668
|
zoomPan: external_exports.object({
|
|
@@ -23828,7 +23670,7 @@ var slideshowStyleSchema2 = external_exports.object({
|
|
|
23828
23670
|
to: external_exports.number().optional(),
|
|
23829
23671
|
rangeFraction: external_exports.number().optional()
|
|
23830
23672
|
}).optional(),
|
|
23831
|
-
easing: external_exports.enum(
|
|
23673
|
+
easing: external_exports.enum(KEN_BURNS_EASINGS).optional(),
|
|
23832
23674
|
blurPad: external_exports.object({
|
|
23833
23675
|
blurPx: external_exports.number().optional(),
|
|
23834
23676
|
brightness: external_exports.number().optional(),
|
|
@@ -23848,43 +23690,11 @@ function registerGenerateVideoTool(server) {
|
|
|
23848
23690
|
"clipform_generate_video",
|
|
23849
23691
|
{
|
|
23850
23692
|
title: "Generate Video",
|
|
23851
|
-
description: `Generate a video from images, video clips, or
|
|
23852
|
-
|
|
23853
|
-
## Workflow
|
|
23854
|
-
|
|
23855
|
-
1. Source media with clipform_search_media (kind: "image" or "video")
|
|
23856
|
-
2. Source audio with clipform_generate_tts (narration) or clipform_search_music (background)
|
|
23857
|
-
3. Call this tool with items + audio_url
|
|
23858
|
-
4. Attach the returned URL to a node via clipform_upload_node_media with media_type "video"
|
|
23859
|
-
|
|
23860
|
-
## Items
|
|
23861
|
-
|
|
23862
|
-
Each item is an image or video clip:
|
|
23863
|
-
- **type: "image"** - still image with Ken Burns motion (pan/zoom). Supports effect, fit, and style overrides.
|
|
23864
|
-
- **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.
|
|
23865
|
-
|
|
23866
|
-
## Style presets (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.
|
|
23867
23694
|
|
|
23868
|
-
|
|
23869
|
-
- **cinematic** (default) - smooth ease-in-out, subtle zoom/pan, subtle vignette
|
|
23870
|
-
- **dramatic** - big zoom, wide pan, ease-out, strong vignette. Reveals, climactic moments.
|
|
23871
|
-
- **calm** - minimal motion, linear easing, no vignette. Educational, reflective.
|
|
23872
|
-
- **documentary** - near-static, tiny pan, no vignette. Historical photos, talking-heads context.
|
|
23873
|
-
- **dreamy** - heavy blur-pad, soft vignette. Aspirational, nostalgic.
|
|
23874
|
-
- **moody** - desaturated, dark, strong vignette. Mystery, somber.
|
|
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".
|
|
23875
23696
|
|
|
23876
|
-
|
|
23877
|
-
|
|
23878
|
-
zoom-in, zoom-out, pan-left, pan-right, pan-up, pan-down, zoom-in-pan-left, zoom-in-pan-right, random, static.
|
|
23879
|
-
Set per-image or use random_effects: true to shuffle.
|
|
23880
|
-
|
|
23881
|
-
## Transitions
|
|
23882
|
-
|
|
23883
|
-
fade (default), slide, wipe, none. Duration in seconds.
|
|
23884
|
-
|
|
23885
|
-
## Duration
|
|
23886
|
-
|
|
23887
|
-
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.`,
|
|
23888
23698
|
inputSchema: {
|
|
23889
23699
|
items: external_exports.array(
|
|
23890
23700
|
external_exports.object({
|
|
@@ -23892,7 +23702,7 @@ Matches audio when audio_url is provided. Use duration_seconds for videos withou
|
|
|
23892
23702
|
url: external_exports.string().url().describe("Media URL"),
|
|
23893
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"),
|
|
23894
23704
|
aspect_ratio: external_exports.number().positive().optional().describe("Image width/height ratio (image only). Enables auto blur-pad for landscape images."),
|
|
23895
|
-
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."),
|
|
23896
23706
|
style: slideshowStyleSchema2.optional().describe("Creative style overrides (image only)"),
|
|
23897
23707
|
start_from: external_exports.number().min(0).optional().describe("Start time in seconds (video only). Trims the clip."),
|
|
23898
23708
|
volume: external_exports.number().min(0).max(1).optional().describe("Clip audio volume 0-1 (video only, default 0 = muted)")
|
|
@@ -23912,8 +23722,7 @@ Matches audio when audio_url is provided. Use duration_seconds for videos withou
|
|
|
23912
23722
|
},
|
|
23913
23723
|
async ({ items, audio_url, duration_seconds, random_effects, transition, default_style, background_color }) => {
|
|
23914
23724
|
const workspace_id = process.env.MCP_WORKSPACE_ID;
|
|
23915
|
-
const
|
|
23916
|
-
callInternalApi("/internal/generate-video", {
|
|
23725
|
+
const result = await callApi("/internal/generate-video", {
|
|
23917
23726
|
body: {
|
|
23918
23727
|
items,
|
|
23919
23728
|
audio_url,
|
|
@@ -23924,76 +23733,19 @@ Matches audio when audio_url is provided. Use duration_seconds for videos withou
|
|
|
23924
23733
|
background_color,
|
|
23925
23734
|
workspace_id
|
|
23926
23735
|
}
|
|
23927
|
-
}).then((result) => {
|
|
23928
|
-
if (!result.ok) {
|
|
23929
|
-
failJob(job.id, result.error);
|
|
23930
|
-
} else {
|
|
23931
|
-
completeJob(job.id, result.data);
|
|
23932
|
-
}
|
|
23933
|
-
}).catch((err) => {
|
|
23934
|
-
failJob(job.id, err instanceof Error ? err.message : String(err));
|
|
23935
23736
|
});
|
|
23737
|
+
if (!result.ok) return errorResult(result.error);
|
|
23738
|
+
const data = result.data;
|
|
23936
23739
|
return textResult(
|
|
23937
23740
|
[
|
|
23938
|
-
`
|
|
23939
|
-
``,
|
|
23940
|
-
`Job ID: ${job.id}`,
|
|
23941
|
-
``,
|
|
23942
|
-
`Renders typically take ${RENDER_TIMING.expectedRange}. Use clipform_check_render with this job ID to check status.`
|
|
23943
|
-
].join("\n")
|
|
23944
|
-
);
|
|
23945
|
-
}
|
|
23946
|
-
);
|
|
23947
|
-
}
|
|
23948
|
-
|
|
23949
|
-
// src/tools/check-render.ts
|
|
23950
|
-
function registerCheckRenderTool(server) {
|
|
23951
|
-
server.registerTool(
|
|
23952
|
-
"clipform_check_render",
|
|
23953
|
-
{
|
|
23954
|
-
title: "Check Render Status",
|
|
23955
|
-
description: `Check the status of a render job started by clipform_generate_video, clipform_generate_slideshow, or clipform_render_composition.
|
|
23956
|
-
|
|
23957
|
-
Returns the current status and, when complete, the output URL. If still rendering, wait ${RENDER_TIMING.pollDelay} before checking again.`,
|
|
23958
|
-
inputSchema: {
|
|
23959
|
-
job_id: external_exports.string().uuid().describe("The job ID returned by the render tool")
|
|
23960
|
-
},
|
|
23961
|
-
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
23962
|
-
},
|
|
23963
|
-
async ({ job_id }) => {
|
|
23964
|
-
pruneJobs();
|
|
23965
|
-
const job = getJob(job_id);
|
|
23966
|
-
if (!job) {
|
|
23967
|
-
return errorResult(`No render job found with ID ${job_id}. Jobs expire after 30 minutes.`);
|
|
23968
|
-
}
|
|
23969
|
-
if (job.status === "rendering") {
|
|
23970
|
-
const elapsed = Math.round((Date.now() - job.createdAt) / 1e3);
|
|
23971
|
-
return textResult(
|
|
23972
|
-
[
|
|
23973
|
-
`Status: rendering (${elapsed}s elapsed)`,
|
|
23974
|
-
`Tool: ${job.tool}`,
|
|
23975
|
-
``,
|
|
23976
|
-
`Still in progress. Check again in ${RENDER_TIMING.pollDelay}.`
|
|
23977
|
-
].join("\n")
|
|
23978
|
-
);
|
|
23979
|
-
}
|
|
23980
|
-
if (job.status === "failed") {
|
|
23981
|
-
return errorResult(`Render failed: ${job.error}`);
|
|
23982
|
-
}
|
|
23983
|
-
const data = job.result;
|
|
23984
|
-
return textResult(
|
|
23985
|
-
[
|
|
23986
|
-
`Status: complete`,
|
|
23987
|
-
`Tool: ${job.tool}`,
|
|
23741
|
+
`Video rendered (${items.length} item${items.length > 1 ? "s" : ""}).`,
|
|
23988
23742
|
``,
|
|
23989
|
-
|
|
23990
|
-
|
|
23991
|
-
|
|
23992
|
-
...data.outputPath ? [`Output: ${data.outputPath}`] : [],
|
|
23993
|
-
...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` : "",
|
|
23994
23746
|
``,
|
|
23995
|
-
`Use clipform_upload_node_media with the public URL to attach
|
|
23996
|
-
].join("\n")
|
|
23747
|
+
`Use clipform_upload_node_media with the public URL to attach to a node.`
|
|
23748
|
+
].filter(Boolean).join("\n")
|
|
23997
23749
|
);
|
|
23998
23750
|
}
|
|
23999
23751
|
);
|
|
@@ -24136,6 +23888,11 @@ async function getSessionContext() {
|
|
|
24136
23888
|
} else {
|
|
24137
23889
|
lines.push("Questions: unlimited");
|
|
24138
23890
|
}
|
|
23891
|
+
if (plan.form_limit !== null) {
|
|
23892
|
+
lines.push(`Form limit: ${plan.form_limit} forms`);
|
|
23893
|
+
} else {
|
|
23894
|
+
lines.push("Forms: unlimited");
|
|
23895
|
+
}
|
|
24139
23896
|
if (plan.custom_theme === false) {
|
|
24140
23897
|
lines.push("Custom themes: not available on this plan");
|
|
24141
23898
|
}
|
|
@@ -25016,7 +24773,6 @@ function createServer() {
|
|
|
25016
24773
|
registerGetNodeMediaTool(server);
|
|
25017
24774
|
registerDeleteNodeMediaTool(server);
|
|
25018
24775
|
registerSetNodeLogicTool(server);
|
|
25019
|
-
registerAttachNodeAudioTool(server);
|
|
25020
24776
|
registerLogGenerationTool(server);
|
|
25021
24777
|
registerSearchNewsTool(server);
|
|
25022
24778
|
registerYouTubeTranscriptTool(server);
|
|
@@ -25028,7 +24784,6 @@ function createServer() {
|
|
|
25028
24784
|
registerSearchMusicTool(server);
|
|
25029
24785
|
registerListCompositionsTool(server);
|
|
25030
24786
|
registerListAssetsTool(server);
|
|
25031
|
-
registerCheckRenderTool(server);
|
|
25032
24787
|
registerFetchBoundaryTool(server);
|
|
25033
24788
|
registerPrompts(server);
|
|
25034
24789
|
registerResources(server);
|
|
@@ -25037,7 +24792,6 @@ function createServer() {
|
|
|
25037
24792
|
|
|
25038
24793
|
export {
|
|
25039
24794
|
JSONRPCMessageSchema,
|
|
25040
|
-
setApiKey,
|
|
25041
24795
|
createServer
|
|
25042
24796
|
};
|
|
25043
|
-
//# sourceMappingURL=chunk-
|
|
24797
|
+
//# sourceMappingURL=chunk-MB6BZ2JN.js.map
|