@clipform/mcp-server 1.47.0 → 1.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-KP64A2KW.js → chunk-3MTEM3VC.js} +440 -68
- package/dist/{chunk-KP64A2KW.js.map → chunk-3MTEM3VC.js.map} +1 -1
- package/dist/{chunk-F2JA2N5N.js → chunk-4CY5EJCR.js} +56 -6
- package/dist/chunk-4CY5EJCR.js.map +1 -0
- package/dist/{chunk-L3YIUY5R.js → chunk-BBNREY2Q.js} +2 -2
- package/dist/{chunk-MOKUP5DR.js → chunk-WUORO245.js} +2 -2
- package/dist/index.js +4 -4
- package/dist/prompts.js +2 -2
- package/dist/resources.js +2 -2
- package/dist/server.js +4 -4
- package/package.json +3 -2
- package/dist/chunk-F2JA2N5N.js.map +0 -1
- /package/dist/{chunk-L3YIUY5R.js.map → chunk-BBNREY2Q.js.map} +0 -0
- /package/dist/{chunk-MOKUP5DR.js.map → chunk-WUORO245.js.map} +0 -0
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getWorkflowText,
|
|
7
7
|
objectType,
|
|
8
8
|
registerPrompts
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-BBNREY2Q.js";
|
|
10
10
|
import {
|
|
11
11
|
GUIDE_TYPES,
|
|
12
12
|
QUIZ_VARIANTS,
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
getGuideUri,
|
|
15
15
|
guideFallbackText,
|
|
16
16
|
registerResources
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-WUORO245.js";
|
|
18
18
|
import {
|
|
19
19
|
ACTIVE_NODE_TYPES,
|
|
20
20
|
BUSINESS,
|
|
@@ -33,8 +33,9 @@ import {
|
|
|
33
33
|
callApi,
|
|
34
34
|
errorResult,
|
|
35
35
|
resolveFormType,
|
|
36
|
+
structuredResult,
|
|
36
37
|
textResult
|
|
37
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-4CY5EJCR.js";
|
|
38
39
|
import {
|
|
39
40
|
__commonJS,
|
|
40
41
|
__export,
|
|
@@ -17325,6 +17326,21 @@ function registerListFormsTool(server) {
|
|
|
17325
17326
|
sort: external_exports.enum(["created_at", "updated_at"]).optional().describe("Sort field (default: created_at)"),
|
|
17326
17327
|
order: external_exports.enum(["asc", "desc"]).optional().describe("Sort order (default: desc, newest first)")
|
|
17327
17328
|
},
|
|
17329
|
+
outputSchema: {
|
|
17330
|
+
forms: external_exports.array(
|
|
17331
|
+
external_exports.object({
|
|
17332
|
+
id: external_exports.string(),
|
|
17333
|
+
title: external_exports.string(),
|
|
17334
|
+
share_id: external_exports.string(),
|
|
17335
|
+
is_live: external_exports.boolean(),
|
|
17336
|
+
has_unpublished_changes: external_exports.boolean(),
|
|
17337
|
+
created_at: external_exports.string(),
|
|
17338
|
+
updated_at: external_exports.string(),
|
|
17339
|
+
tags: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string(), color: external_exports.string().nullable() }))
|
|
17340
|
+
})
|
|
17341
|
+
).describe("Matching forms (empty if none)"),
|
|
17342
|
+
next_cursor: external_exports.string().nullable().describe("Pass as cursor to fetch the next page, or null when no more")
|
|
17343
|
+
},
|
|
17328
17344
|
annotations: {
|
|
17329
17345
|
readOnlyHint: true,
|
|
17330
17346
|
destructiveHint: false,
|
|
@@ -17346,8 +17362,21 @@ function registerListFormsTool(server) {
|
|
|
17346
17362
|
return errorResult(result.error);
|
|
17347
17363
|
}
|
|
17348
17364
|
const data = result.data;
|
|
17365
|
+
const structured = {
|
|
17366
|
+
forms: data.forms.map((f) => ({
|
|
17367
|
+
id: f.id,
|
|
17368
|
+
title: f.title,
|
|
17369
|
+
share_id: f.share_id,
|
|
17370
|
+
is_live: f.is_live,
|
|
17371
|
+
has_unpublished_changes: f.has_unpublished_changes,
|
|
17372
|
+
created_at: f.created_at,
|
|
17373
|
+
updated_at: f.updated_at,
|
|
17374
|
+
tags: f.tags.map((t) => ({ id: t.id, name: t.name, color: t.color }))
|
|
17375
|
+
})),
|
|
17376
|
+
next_cursor: data.next_cursor
|
|
17377
|
+
};
|
|
17349
17378
|
if (data.forms.length === 0) {
|
|
17350
|
-
return
|
|
17379
|
+
return structuredResult("No forms found matching the criteria.", structured);
|
|
17351
17380
|
}
|
|
17352
17381
|
const lines = [`Found ${data.forms.length} form(s):
|
|
17353
17382
|
`];
|
|
@@ -17361,14 +17390,36 @@ function registerListFormsTool(server) {
|
|
|
17361
17390
|
`More results available. Pass cursor: "${data.next_cursor}" to get the next page.`
|
|
17362
17391
|
);
|
|
17363
17392
|
}
|
|
17364
|
-
return
|
|
17393
|
+
return structuredResult(lines.join("\n"), structured);
|
|
17365
17394
|
}
|
|
17366
17395
|
);
|
|
17367
17396
|
}
|
|
17368
17397
|
|
|
17369
17398
|
// src/lib/format-form.ts
|
|
17399
|
+
function getFormNodes(data) {
|
|
17400
|
+
return data.node ?? data.nodes ?? [];
|
|
17401
|
+
}
|
|
17402
|
+
function projectFormState(data) {
|
|
17403
|
+
return {
|
|
17404
|
+
form_id: String(data.form_id ?? ""),
|
|
17405
|
+
title: String(data.title ?? ""),
|
|
17406
|
+
is_live: Boolean(data.is_live),
|
|
17407
|
+
has_unpublished_changes: Boolean(data.has_unpublished_changes),
|
|
17408
|
+
nodes: getFormNodes(data).map((q) => {
|
|
17409
|
+
const options = q.node_options ?? q.options;
|
|
17410
|
+
return {
|
|
17411
|
+
id: q.id,
|
|
17412
|
+
type: q.type,
|
|
17413
|
+
prompt: q.prompt ?? "",
|
|
17414
|
+
required: Boolean(q.required),
|
|
17415
|
+
...q.config && Object.keys(q.config).length > 0 ? { config: q.config } : {},
|
|
17416
|
+
...options && options.length > 0 ? { options: options.map((o) => ({ id: o.id, content: o.content })) } : {}
|
|
17417
|
+
};
|
|
17418
|
+
})
|
|
17419
|
+
};
|
|
17420
|
+
}
|
|
17370
17421
|
function formatFormState(data) {
|
|
17371
|
-
const nodes = data
|
|
17422
|
+
const nodes = getFormNodes(data);
|
|
17372
17423
|
const lines = [
|
|
17373
17424
|
`Form: ${data.title}`,
|
|
17374
17425
|
`Form ID: ${data.form_id}`,
|
|
@@ -17408,6 +17459,22 @@ function registerGetFormTool(server) {
|
|
|
17408
17459
|
inputSchema: {
|
|
17409
17460
|
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)")
|
|
17410
17461
|
},
|
|
17462
|
+
outputSchema: {
|
|
17463
|
+
form_id: external_exports.string().describe("Form UUID"),
|
|
17464
|
+
title: external_exports.string().describe("Form title"),
|
|
17465
|
+
is_live: external_exports.boolean().describe("Whether the form is published"),
|
|
17466
|
+
has_unpublished_changes: external_exports.boolean().describe("Live form has edits not yet republished"),
|
|
17467
|
+
nodes: external_exports.array(
|
|
17468
|
+
external_exports.object({
|
|
17469
|
+
id: external_exports.string(),
|
|
17470
|
+
type: external_exports.string(),
|
|
17471
|
+
prompt: external_exports.string(),
|
|
17472
|
+
required: external_exports.boolean(),
|
|
17473
|
+
config: external_exports.record(external_exports.unknown()).optional(),
|
|
17474
|
+
options: external_exports.array(external_exports.object({ id: external_exports.string(), content: external_exports.string() })).optional()
|
|
17475
|
+
})
|
|
17476
|
+
).describe("Nodes in sequential order - IDs for update_node / upload_node_media")
|
|
17477
|
+
},
|
|
17411
17478
|
annotations: {
|
|
17412
17479
|
readOnlyHint: true,
|
|
17413
17480
|
destructiveHint: false,
|
|
@@ -17420,7 +17487,7 @@ function registerGetFormTool(server) {
|
|
|
17420
17487
|
if (!result.ok) {
|
|
17421
17488
|
return errorResult(result.error);
|
|
17422
17489
|
}
|
|
17423
|
-
return
|
|
17490
|
+
return structuredResult(formatFormState(result.data), projectFormState(result.data));
|
|
17424
17491
|
}
|
|
17425
17492
|
);
|
|
17426
17493
|
}
|
|
@@ -17597,6 +17664,10 @@ function registerAddNodeTool(server) {
|
|
|
17597
17664
|
"Insert after this node ID. Omit to append before the end screen."
|
|
17598
17665
|
)
|
|
17599
17666
|
},
|
|
17667
|
+
outputSchema: {
|
|
17668
|
+
node_id: external_exports.string().describe("ID of the created node - pass to update_node / upload_node_media"),
|
|
17669
|
+
type: external_exports.string().describe("Node type that was added")
|
|
17670
|
+
},
|
|
17600
17671
|
annotations: {
|
|
17601
17672
|
readOnlyHint: false,
|
|
17602
17673
|
destructiveHint: false,
|
|
@@ -17615,8 +17686,10 @@ function registerAddNodeTool(server) {
|
|
|
17615
17686
|
if (!result.ok) {
|
|
17616
17687
|
return errorResult(result.error);
|
|
17617
17688
|
}
|
|
17618
|
-
|
|
17619
|
-
|
|
17689
|
+
const nodeId = result.data.node_id;
|
|
17690
|
+
return structuredResult(
|
|
17691
|
+
`Node added. ID: ${nodeId} | Type: ${node.type}`,
|
|
17692
|
+
{ node_id: nodeId, type: node.type }
|
|
17620
17693
|
);
|
|
17621
17694
|
}
|
|
17622
17695
|
);
|
|
@@ -17644,6 +17717,16 @@ function registerUpdateNodeTool(server) {
|
|
|
17644
17717
|
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
17645
17718
|
updates: external_exports.array(NodeUpdateSchema).min(1).max(20).describe("One or more node updates to apply")
|
|
17646
17719
|
},
|
|
17720
|
+
outputSchema: {
|
|
17721
|
+
results: external_exports.array(
|
|
17722
|
+
external_exports.object({
|
|
17723
|
+
node_id: external_exports.string(),
|
|
17724
|
+
ok: external_exports.boolean(),
|
|
17725
|
+
warnings: external_exports.array(external_exports.string()).optional(),
|
|
17726
|
+
error: external_exports.string().optional()
|
|
17727
|
+
})
|
|
17728
|
+
).describe("One result per requested update, in order")
|
|
17729
|
+
},
|
|
17647
17730
|
annotations: {
|
|
17648
17731
|
readOnlyHint: false,
|
|
17649
17732
|
destructiveHint: false,
|
|
@@ -17653,6 +17736,7 @@ function registerUpdateNodeTool(server) {
|
|
|
17653
17736
|
},
|
|
17654
17737
|
async ({ form_id, updates }) => {
|
|
17655
17738
|
const allLines = [];
|
|
17739
|
+
const results = [];
|
|
17656
17740
|
for (const { node_id, prompt, label, type, required: required2, config: config2, options } of updates) {
|
|
17657
17741
|
const body = {};
|
|
17658
17742
|
if (prompt !== void 0) body.prompt = prompt;
|
|
@@ -17667,6 +17751,7 @@ function registerUpdateNodeTool(server) {
|
|
|
17667
17751
|
);
|
|
17668
17752
|
if (!result.ok) {
|
|
17669
17753
|
allLines.push(`Node ${node_id}: FAILED \u2014 ${result.error}`);
|
|
17754
|
+
results.push({ node_id, ok: false, error: result.error });
|
|
17670
17755
|
continue;
|
|
17671
17756
|
}
|
|
17672
17757
|
const changes = [];
|
|
@@ -17682,8 +17767,9 @@ function registerUpdateNodeTool(server) {
|
|
|
17682
17767
|
if (warnings?.length) {
|
|
17683
17768
|
for (const w of warnings) allLines.push(`Node ${node_id} warning: ${w}`);
|
|
17684
17769
|
}
|
|
17770
|
+
results.push({ node_id, ok: true, ...warnings?.length ? { warnings } : {} });
|
|
17685
17771
|
}
|
|
17686
|
-
return
|
|
17772
|
+
return structuredResult(allLines.join("\n"), { results });
|
|
17687
17773
|
}
|
|
17688
17774
|
);
|
|
17689
17775
|
}
|
|
@@ -17749,7 +17835,7 @@ var MediaItemSchema = external_exports.object({
|
|
|
17749
17835
|
).optional().describe("Per-word timestamps within the segment")
|
|
17750
17836
|
})
|
|
17751
17837
|
).optional().describe("Word-level captions from clipform_generate_tts. Required for per-word highlighting - pass the full objects including 'words' arrays."),
|
|
17752
|
-
show_captions: external_exports.boolean().optional().
|
|
17838
|
+
show_captions: external_exports.boolean().optional().describe("Display captions/subtitles on the node. Defaults to on; pass false to hide them."),
|
|
17753
17839
|
fit_media: external_exports.boolean().optional().describe("Show the whole frame (contain) instead of cover-cropping. ALWAYS set true when attaching renders from clipform_render_composition or clipform_generate_video - they are composed 9:16 frames that must never be cropped. Leave unset for user-supplied media (arbitrary aspect ratios want the cover default).")
|
|
17754
17840
|
});
|
|
17755
17841
|
function registerUploadNodeMediaTool(server) {
|
|
@@ -17759,11 +17845,23 @@ function registerUploadNodeMediaTool(server) {
|
|
|
17759
17845
|
title: "Upload Node Media",
|
|
17760
17846
|
description: `Upload media for one or more nodes. Pass one item or many (max 10). Multiple items upload sequentially.
|
|
17761
17847
|
|
|
17762
|
-
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. Captions from clipform_generate_tts enable per-word highlighting in the viewer. When attaching renders from clipform_render_composition or clipform_generate_video, set fit_media: true on each item.`,
|
|
17848
|
+
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. Each upload lands in your workspace media library and is then attached to the node (so it is reusable across nodes), matching the builder. Captions from clipform_generate_tts enable per-word highlighting in the viewer. When attaching renders from clipform_render_composition or clipform_generate_video, set fit_media: true on each item.`,
|
|
17763
17849
|
inputSchema: {
|
|
17764
17850
|
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
17765
17851
|
items: external_exports.array(MediaItemSchema).min(1).max(10).describe("One or more media items to upload")
|
|
17766
17852
|
},
|
|
17853
|
+
outputSchema: {
|
|
17854
|
+
results: external_exports.array(
|
|
17855
|
+
external_exports.object({
|
|
17856
|
+
node_id: external_exports.string(),
|
|
17857
|
+
ok: external_exports.boolean(),
|
|
17858
|
+
stored: external_exports.boolean().describe("Bytes already stored (URL-based); false when an upload_url is pending"),
|
|
17859
|
+
upload_url: external_exports.string().optional(),
|
|
17860
|
+
upload_method: external_exports.string().optional(),
|
|
17861
|
+
error: external_exports.string().optional()
|
|
17862
|
+
})
|
|
17863
|
+
).describe("One result per item, in order")
|
|
17864
|
+
},
|
|
17767
17865
|
annotations: {
|
|
17768
17866
|
readOnlyHint: false,
|
|
17769
17867
|
destructiveHint: false,
|
|
@@ -17772,42 +17870,89 @@ When a public URL is provided, the media is fetched and stored automatically. On
|
|
|
17772
17870
|
}
|
|
17773
17871
|
},
|
|
17774
17872
|
async ({ form_id, items }) => {
|
|
17873
|
+
const workspaceId = getMcpAuth()?.workspace_id;
|
|
17775
17874
|
const lines = [];
|
|
17875
|
+
const results = [];
|
|
17776
17876
|
let successCount = 0;
|
|
17777
17877
|
for (let i = 0; i < items.length; i++) {
|
|
17778
17878
|
const item = items[i];
|
|
17779
17879
|
if (items.length > 1) lines.push(`--- Item ${i + 1} (node ${item.node_id}) ---`);
|
|
17780
|
-
|
|
17880
|
+
if (!workspaceId) {
|
|
17881
|
+
const error2 = "no workspace in context - reconnect the MCP or set CLIPFORM_API_KEY so media can land in your library.";
|
|
17882
|
+
lines.push(`FAILED: ${error2}`);
|
|
17883
|
+
lines.push("");
|
|
17884
|
+
results.push({ node_id: item.node_id, ok: false, stored: false, error: error2 });
|
|
17885
|
+
continue;
|
|
17886
|
+
}
|
|
17887
|
+
const createBody = {
|
|
17781
17888
|
media_type: item.media_type,
|
|
17782
17889
|
media_source: item.media_source
|
|
17783
17890
|
};
|
|
17784
|
-
if (item.url)
|
|
17785
|
-
if (item.captions)
|
|
17786
|
-
|
|
17787
|
-
if (
|
|
17788
|
-
|
|
17789
|
-
|
|
17790
|
-
{
|
|
17791
|
-
|
|
17792
|
-
|
|
17793
|
-
|
|
17794
|
-
|
|
17795
|
-
|
|
17796
|
-
|
|
17797
|
-
|
|
17798
|
-
|
|
17799
|
-
|
|
17800
|
-
|
|
17801
|
-
|
|
17802
|
-
|
|
17803
|
-
}
|
|
17804
|
-
|
|
17805
|
-
|
|
17891
|
+
if (item.url) createBody.url = item.url;
|
|
17892
|
+
if (item.captions) createBody.captions = item.captions;
|
|
17893
|
+
const created = await callApi(`/workspaces/${workspaceId}/media`, { method: "POST", body: createBody });
|
|
17894
|
+
if (!created.ok) {
|
|
17895
|
+
lines.push(`FAILED: ${created.error}`);
|
|
17896
|
+
lines.push("");
|
|
17897
|
+
results.push({ node_id: item.node_id, ok: false, stored: false, error: created.error });
|
|
17898
|
+
continue;
|
|
17899
|
+
}
|
|
17900
|
+
const mediaAssetId = created.data.media_asset_id;
|
|
17901
|
+
if (!mediaAssetId) {
|
|
17902
|
+
const error2 = `upload did not return a media asset id (route ${String(created.data.route ?? "unknown")}).`;
|
|
17903
|
+
lines.push(`FAILED: ${error2}`);
|
|
17904
|
+
lines.push("");
|
|
17905
|
+
results.push({ node_id: item.node_id, ok: false, stored: false, error: error2 });
|
|
17906
|
+
continue;
|
|
17907
|
+
}
|
|
17908
|
+
const attached = await callApi(`/forms/${form_id}/nodes/${item.node_id}/media/attach`, {
|
|
17909
|
+
method: "POST",
|
|
17910
|
+
body: { media_asset_id: mediaAssetId }
|
|
17911
|
+
});
|
|
17912
|
+
if (!attached.ok) {
|
|
17913
|
+
lines.push(`FAILED to attach the uploaded media to the node: ${attached.error}`);
|
|
17914
|
+
lines.push("");
|
|
17915
|
+
results.push({ node_id: item.node_id, ok: false, stored: false, error: attached.error });
|
|
17916
|
+
continue;
|
|
17917
|
+
}
|
|
17918
|
+
const presentation = {};
|
|
17919
|
+
if (item.fit_media !== void 0) presentation.fit_media = item.fit_media;
|
|
17920
|
+
if (item.show_captions !== void 0) presentation.show_captions = item.show_captions;
|
|
17921
|
+
let presentationNote = "";
|
|
17922
|
+
if (Object.keys(presentation).length > 0) {
|
|
17923
|
+
const patched = await callApi(`/forms/${form_id}/nodes/${item.node_id}/media`, {
|
|
17924
|
+
method: "PATCH",
|
|
17925
|
+
body: presentation
|
|
17926
|
+
});
|
|
17927
|
+
if (!patched.ok) {
|
|
17928
|
+
presentationNote = `Note: media attached, but display settings were not applied: ${patched.error}`;
|
|
17806
17929
|
}
|
|
17807
|
-
|
|
17930
|
+
}
|
|
17931
|
+
successCount++;
|
|
17932
|
+
const uploadUrl = created.data.upload_url;
|
|
17933
|
+
const uploadMethod = created.data.upload_method;
|
|
17934
|
+
results.push({
|
|
17935
|
+
node_id: item.node_id,
|
|
17936
|
+
ok: true,
|
|
17937
|
+
stored: !uploadUrl,
|
|
17938
|
+
...uploadUrl ? { upload_url: uploadUrl } : {},
|
|
17939
|
+
...uploadUrl && uploadMethod ? { upload_method: uploadMethod } : {}
|
|
17940
|
+
});
|
|
17941
|
+
const resultLines = [];
|
|
17942
|
+
if (created.data.upload_url) {
|
|
17943
|
+
resultLines.push(
|
|
17944
|
+
`Upload URL: ${created.data.upload_url}`,
|
|
17945
|
+
`Upload method: ${created.data.upload_method}`,
|
|
17946
|
+
`Upload the file directly to the upload URL using ${created.data.upload_method === "tus" ? "TUS resumable upload" : "HTTP PUT"}.`
|
|
17947
|
+
);
|
|
17808
17948
|
} else {
|
|
17809
|
-
|
|
17949
|
+
resultLines.push(`Media stored in the workspace library and attached to the node.`);
|
|
17810
17950
|
}
|
|
17951
|
+
if (item.captions) {
|
|
17952
|
+
resultLines.push(`Captions: ${item.captions.length} segments saved`);
|
|
17953
|
+
}
|
|
17954
|
+
if (presentationNote) resultLines.push(presentationNote);
|
|
17955
|
+
lines.push(resultLines.join("\n"));
|
|
17811
17956
|
lines.push("");
|
|
17812
17957
|
}
|
|
17813
17958
|
if (items.length > 1) {
|
|
@@ -17815,7 +17960,7 @@ When a public URL is provided, the media is fetched and stored automatically. On
|
|
|
17815
17960
|
`);
|
|
17816
17961
|
}
|
|
17817
17962
|
if (successCount === 0) return errorResult(lines.join("\n"));
|
|
17818
|
-
return
|
|
17963
|
+
return structuredResult(lines.join("\n"), { results });
|
|
17819
17964
|
}
|
|
17820
17965
|
);
|
|
17821
17966
|
}
|
|
@@ -17831,6 +17976,14 @@ function registerGetNodeMediaTool(server) {
|
|
|
17831
17976
|
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
17832
17977
|
node_id: external_exports.string().describe("The node ID")
|
|
17833
17978
|
},
|
|
17979
|
+
outputSchema: {
|
|
17980
|
+
media: external_exports.object({
|
|
17981
|
+
media_type: external_exports.string(),
|
|
17982
|
+
status: external_exports.string(),
|
|
17983
|
+
duration: external_exports.number().optional(),
|
|
17984
|
+
transcription_status: external_exports.string().nullable().optional()
|
|
17985
|
+
}).nullable().describe("Attached media (null if the node has none)")
|
|
17986
|
+
},
|
|
17834
17987
|
annotations: {
|
|
17835
17988
|
readOnlyHint: true,
|
|
17836
17989
|
destructiveHint: false,
|
|
@@ -17847,15 +18000,22 @@ function registerGetNodeMediaTool(server) {
|
|
|
17847
18000
|
}
|
|
17848
18001
|
const media = result.data.media;
|
|
17849
18002
|
if (!media) {
|
|
17850
|
-
return
|
|
18003
|
+
return structuredResult("No media attached to this node.", { media: null });
|
|
17851
18004
|
}
|
|
17852
|
-
|
|
18005
|
+
const projected = {
|
|
18006
|
+
media_type: String(media.media_type),
|
|
18007
|
+
status: String(media.status),
|
|
18008
|
+
...typeof media.duration === "number" ? { duration: media.duration } : {},
|
|
18009
|
+
transcription_status: media.transcription_status == null ? null : String(media.transcription_status)
|
|
18010
|
+
};
|
|
18011
|
+
return structuredResult(
|
|
17853
18012
|
[
|
|
17854
18013
|
`Type: ${media.media_type}`,
|
|
17855
18014
|
`Status: ${media.status}`,
|
|
17856
18015
|
media.duration ? `Duration: ${media.duration}s` : "",
|
|
17857
18016
|
`Transcription: ${media.transcription_status}`
|
|
17858
|
-
].filter(Boolean).join("\n")
|
|
18017
|
+
].filter(Boolean).join("\n"),
|
|
18018
|
+
{ media: projected }
|
|
17859
18019
|
);
|
|
17860
18020
|
}
|
|
17861
18021
|
);
|
|
@@ -18009,6 +18169,20 @@ Returns: article title, source, author, date, URL, description, and image URL pe
|
|
|
18009
18169
|
query: external_exports.string().describe("News search query (e.g. 'Iran war 2026', 'Australian Open final', 'UK election')"),
|
|
18010
18170
|
count: external_exports.number().min(1).max(15).default(5).optional().describe("Max results per provider (default 5)")
|
|
18011
18171
|
},
|
|
18172
|
+
outputSchema: {
|
|
18173
|
+
query: external_exports.string(),
|
|
18174
|
+
articles: external_exports.array(
|
|
18175
|
+
external_exports.object({
|
|
18176
|
+
title: external_exports.string().optional(),
|
|
18177
|
+
source: external_exports.string().optional(),
|
|
18178
|
+
description: external_exports.string().optional(),
|
|
18179
|
+
author: external_exports.string().optional(),
|
|
18180
|
+
published_at: external_exports.string().optional(),
|
|
18181
|
+
url: external_exports.string(),
|
|
18182
|
+
image_url: external_exports.string().optional()
|
|
18183
|
+
})
|
|
18184
|
+
).describe("Matching articles (empty if none)")
|
|
18185
|
+
},
|
|
18012
18186
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
18013
18187
|
},
|
|
18014
18188
|
async ({ query, count }) => {
|
|
@@ -18017,7 +18191,16 @@ Returns: article title, source, author, date, URL, description, and image URL pe
|
|
|
18017
18191
|
});
|
|
18018
18192
|
if (!result.ok) return errorResult(result.error);
|
|
18019
18193
|
const results = result.data.results;
|
|
18020
|
-
if (!results.length) return
|
|
18194
|
+
if (!results.length) return structuredResult(`No news articles found for "${query}".`, { query, articles: [] });
|
|
18195
|
+
const articles = results.map((article) => ({
|
|
18196
|
+
...article.title ? { title: article.title } : {},
|
|
18197
|
+
...article.source ? { source: article.source } : {},
|
|
18198
|
+
...article.description ? { description: article.description } : {},
|
|
18199
|
+
...article.author ? { author: article.author } : {},
|
|
18200
|
+
...article.publishedAt ? { published_at: article.publishedAt } : {},
|
|
18201
|
+
url: article.url,
|
|
18202
|
+
...article.imageUrl ? { image_url: article.imageUrl } : {}
|
|
18203
|
+
}));
|
|
18021
18204
|
const lines = [`Found ${results.length} articles for "${query}":
|
|
18022
18205
|
`];
|
|
18023
18206
|
for (const article of results) {
|
|
@@ -18029,7 +18212,7 @@ Returns: article title, source, author, date, URL, description, and image URL pe
|
|
|
18029
18212
|
if (article.imageUrl) lines.push(` Image: ${article.imageUrl}`);
|
|
18030
18213
|
lines.push("");
|
|
18031
18214
|
}
|
|
18032
|
-
return
|
|
18215
|
+
return structuredResult(lines.join("\n"), { query, articles });
|
|
18033
18216
|
}
|
|
18034
18217
|
);
|
|
18035
18218
|
}
|
|
@@ -18085,6 +18268,12 @@ Supports any public YouTube video with captions enabled. Does NOT work for priva
|
|
|
18085
18268
|
}
|
|
18086
18269
|
|
|
18087
18270
|
// src/tools/generate-tts.ts
|
|
18271
|
+
var CaptionSchema = external_exports.object({
|
|
18272
|
+
start: external_exports.number(),
|
|
18273
|
+
end: external_exports.number(),
|
|
18274
|
+
text: external_exports.string(),
|
|
18275
|
+
words: external_exports.array(external_exports.object({ word: external_exports.string(), start: external_exports.number(), end: external_exports.number() })).optional()
|
|
18276
|
+
});
|
|
18088
18277
|
var TtsItemSchema = external_exports.object({
|
|
18089
18278
|
text: external_exports.string().min(1).max(5e3).describe("Narration text"),
|
|
18090
18279
|
voice: external_exports.enum(["ryan", "sonia", "andrew", "ava", "guy"]).optional().default("ryan").describe("Voice: ryan (British male, clear/articulate), sonia (British female, warm/bright), andrew (American male, smooth/neutral), ava (American female, vibrant/friendly), guy (American male, deep/authoritative). Pick based on the form's topic and audience."),
|
|
@@ -18105,6 +18294,16 @@ Pass one item or many (max 10) - multiple items run in parallel. Returns audio U
|
|
|
18105
18294
|
inputSchema: {
|
|
18106
18295
|
items: external_exports.array(TtsItemSchema).min(1).max(10).describe("One or more TTS items to generate")
|
|
18107
18296
|
},
|
|
18297
|
+
outputSchema: {
|
|
18298
|
+
results: external_exports.array(
|
|
18299
|
+
external_exports.object({
|
|
18300
|
+
ok: external_exports.boolean(),
|
|
18301
|
+
audio_url: external_exports.string().optional(),
|
|
18302
|
+
captions: external_exports.array(CaptionSchema).optional().describe("Pass as the captions param to upload_node_media"),
|
|
18303
|
+
error: external_exports.string().optional()
|
|
18304
|
+
})
|
|
18305
|
+
).describe("One result per item, in order")
|
|
18306
|
+
},
|
|
18108
18307
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
18109
18308
|
},
|
|
18110
18309
|
async ({ items }) => {
|
|
@@ -18118,6 +18317,7 @@ Pass one item or many (max 10) - multiple items run in parallel. Returns audio U
|
|
|
18118
18317
|
)
|
|
18119
18318
|
);
|
|
18120
18319
|
const lines = [];
|
|
18320
|
+
const structured = [];
|
|
18121
18321
|
let successCount = 0;
|
|
18122
18322
|
for (let i = 0; i < results.length; i++) {
|
|
18123
18323
|
const r = results[i];
|
|
@@ -18134,11 +18334,17 @@ Pass one item or many (max 10) - multiple items run in parallel. Returns audio U
|
|
|
18134
18334
|
...Array.isArray(c.words) ? { words: c.words.map((w) => ({ word: w.word, start: w.start, end: w.end })) } : {}
|
|
18135
18335
|
}));
|
|
18136
18336
|
lines.push(`Captions: ${JSON.stringify(captions)}`);
|
|
18337
|
+
structured.push({
|
|
18338
|
+
ok: true,
|
|
18339
|
+
...data.audioUrl ? { audio_url: String(data.audioUrl) } : {},
|
|
18340
|
+
captions
|
|
18341
|
+
});
|
|
18137
18342
|
} else {
|
|
18138
18343
|
const error2 = r.status === "rejected" ? r.reason?.message || String(r.reason) : r.value.error;
|
|
18139
18344
|
const status = r.status === "fulfilled" ? r.value.status : 0;
|
|
18140
18345
|
const hint = status === 500 || status === 0 ? " (transient - retrying may help)" : "";
|
|
18141
18346
|
lines.push(`FAILED: ${error2}${hint}`);
|
|
18347
|
+
structured.push({ ok: false, error: String(error2) });
|
|
18142
18348
|
}
|
|
18143
18349
|
lines.push("");
|
|
18144
18350
|
}
|
|
@@ -18148,7 +18354,7 @@ Pass one item or many (max 10) - multiple items run in parallel. Returns audio U
|
|
|
18148
18354
|
}
|
|
18149
18355
|
lines.push(`Pass the complete Captions JSON as the "captions" parameter when uploading.`);
|
|
18150
18356
|
if (successCount === 0) return errorResult(lines.join("\n"));
|
|
18151
|
-
return
|
|
18357
|
+
return structuredResult(lines.join("\n"), { results: structured });
|
|
18152
18358
|
}
|
|
18153
18359
|
);
|
|
18154
18360
|
}
|
|
@@ -18172,10 +18378,30 @@ Example: { queries: [{ query: "saturn rings" }, { query: "mars surface", count:
|
|
|
18172
18378
|
})
|
|
18173
18379
|
).min(1).max(10).describe("One or more search queries to run")
|
|
18174
18380
|
},
|
|
18381
|
+
outputSchema: {
|
|
18382
|
+
searches: external_exports.array(
|
|
18383
|
+
external_exports.object({
|
|
18384
|
+
query: external_exports.string(),
|
|
18385
|
+
kind: external_exports.enum(["image", "video"]),
|
|
18386
|
+
items: external_exports.array(
|
|
18387
|
+
external_exports.object({
|
|
18388
|
+
title: external_exports.string().optional(),
|
|
18389
|
+
url: external_exports.string(),
|
|
18390
|
+
width: external_exports.number().optional(),
|
|
18391
|
+
height: external_exports.number().optional(),
|
|
18392
|
+
duration: external_exports.number().optional(),
|
|
18393
|
+
source: external_exports.string().optional(),
|
|
18394
|
+
attribution: external_exports.string().optional()
|
|
18395
|
+
})
|
|
18396
|
+
)
|
|
18397
|
+
})
|
|
18398
|
+
).describe("Results grouped by query (items empty if a query found nothing or failed)")
|
|
18399
|
+
},
|
|
18175
18400
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
18176
18401
|
},
|
|
18177
18402
|
async ({ queries }) => {
|
|
18178
18403
|
const allLines = [];
|
|
18404
|
+
const searches = [];
|
|
18179
18405
|
for (const q of queries) {
|
|
18180
18406
|
const { query, count, orientation } = q;
|
|
18181
18407
|
const kind = q.kind ?? "image";
|
|
@@ -18185,16 +18411,32 @@ Example: { queries: [{ query: "saturn rings" }, { query: "mars surface", count:
|
|
|
18185
18411
|
if (queries.length > 1) allLines.push(`--- "${query}" (${kind}) ---`);
|
|
18186
18412
|
if (!result.ok) {
|
|
18187
18413
|
allLines.push(`FAILED: ${result.error}`, "");
|
|
18414
|
+
searches.push({ query, kind, items: [] });
|
|
18188
18415
|
continue;
|
|
18189
18416
|
}
|
|
18190
18417
|
const results = result.data.results;
|
|
18191
18418
|
if (!results.length) {
|
|
18192
18419
|
allLines.push(`No ${kind}s found.`, "");
|
|
18420
|
+
searches.push({ query, kind, items: [] });
|
|
18193
18421
|
continue;
|
|
18194
18422
|
}
|
|
18423
|
+
const shown = results.slice(0, 10);
|
|
18424
|
+
searches.push({
|
|
18425
|
+
query,
|
|
18426
|
+
kind,
|
|
18427
|
+
items: shown.map((item) => ({
|
|
18428
|
+
...item.title ? { title: item.title } : {},
|
|
18429
|
+
url: item.url,
|
|
18430
|
+
...typeof item.width === "number" ? { width: item.width } : {},
|
|
18431
|
+
...typeof item.height === "number" ? { height: item.height } : {},
|
|
18432
|
+
...kind === "video" && typeof item.duration === "number" ? { duration: item.duration } : {},
|
|
18433
|
+
...item.source ? { source: item.source } : {},
|
|
18434
|
+
...item.attribution ? { attribution: item.attribution } : {}
|
|
18435
|
+
}))
|
|
18436
|
+
});
|
|
18195
18437
|
allLines.push(`Found ${results.length} ${kind}s:
|
|
18196
18438
|
`);
|
|
18197
|
-
for (const item of
|
|
18439
|
+
for (const item of shown) {
|
|
18198
18440
|
allLines.push(`- ${item.title}`);
|
|
18199
18441
|
allLines.push(` URL: ${item.url}`);
|
|
18200
18442
|
const meta = [];
|
|
@@ -18209,7 +18451,7 @@ Example: { queries: [{ query: "saturn rings" }, { query: "mars surface", count:
|
|
|
18209
18451
|
allLines.push("");
|
|
18210
18452
|
}
|
|
18211
18453
|
}
|
|
18212
|
-
return
|
|
18454
|
+
return structuredResult(allLines.join("\n"), { searches });
|
|
18213
18455
|
}
|
|
18214
18456
|
);
|
|
18215
18457
|
}
|
|
@@ -18345,6 +18587,17 @@ For multi-render builds (e.g. one clip per quiz question), pass everything in ON
|
|
|
18345
18587
|
wait: external_exports.boolean().optional().default(true).describe("Single render only: true (default) blocks until the render is ready and returns its URL; false returns a job ID for clipform_check_render. Batch items always run fire-and-poll."),
|
|
18346
18588
|
items: external_exports.array(RenderItemSchema).min(1).max(10).optional().describe("Batch mode: multiple renders in one call. All fire in parallel; returns one job ID per item - collect with clipform_check_render (job_ids). Use this whenever rendering more than one clip.")
|
|
18347
18589
|
},
|
|
18590
|
+
outputSchema: {
|
|
18591
|
+
jobs: external_exports.array(
|
|
18592
|
+
external_exports.object({
|
|
18593
|
+
status: external_exports.enum(["rendering", "complete"]),
|
|
18594
|
+
job_id: external_exports.string().optional().describe("Present when status is 'rendering' - pass to check_render"),
|
|
18595
|
+
public_url: external_exports.string().optional().describe("Present when status is 'complete' - attach via upload_node_media (fit_media: true)"),
|
|
18596
|
+
composition_id: external_exports.string().optional(),
|
|
18597
|
+
output_format: external_exports.string().optional()
|
|
18598
|
+
})
|
|
18599
|
+
).describe("One entry per render (single or batch)")
|
|
18600
|
+
},
|
|
18348
18601
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
18349
18602
|
},
|
|
18350
18603
|
async ({ compositionId, outputFormat, inputProps, wait, items }) => {
|
|
@@ -18360,23 +18613,34 @@ For multi-render builds (e.g. one clip per quiz question), pass everything in ON
|
|
|
18360
18613
|
}
|
|
18361
18614
|
if (items?.length) {
|
|
18362
18615
|
const jobs2 = items.map((item) => ({ item, job: fireRender(item) }));
|
|
18363
|
-
return
|
|
18616
|
+
return structuredResult(
|
|
18364
18617
|
[
|
|
18365
18618
|
`${jobs2.length} renders started in parallel:`,
|
|
18366
18619
|
...jobs2.map(({ item, job }) => `- ${item.compositionId} (${item.outputFormat}): job ${job.id}`),
|
|
18367
18620
|
``,
|
|
18368
18621
|
`Collect with ONE clipform_check_render call (job_ids: [...]). Renders typically take ${RENDER_TIMING.expectedRange}.`
|
|
18369
|
-
].join("\n")
|
|
18622
|
+
].join("\n"),
|
|
18623
|
+
{
|
|
18624
|
+
jobs: jobs2.map(({ item, job }) => ({
|
|
18625
|
+
status: "rendering",
|
|
18626
|
+
job_id: job.id,
|
|
18627
|
+
composition_id: item.compositionId,
|
|
18628
|
+
output_format: item.outputFormat
|
|
18629
|
+
}))
|
|
18630
|
+
}
|
|
18370
18631
|
);
|
|
18371
18632
|
}
|
|
18372
18633
|
if (wait === false) {
|
|
18373
18634
|
const job = fireRender({ compositionId, outputFormat, inputProps });
|
|
18374
|
-
return
|
|
18635
|
+
return structuredResult(
|
|
18375
18636
|
[
|
|
18376
18637
|
`Render started (${compositionId}).`,
|
|
18377
18638
|
`Job ID: ${job.id}`,
|
|
18378
18639
|
`Fire any remaining renders now, then poll clipform_check_render. Renders typically take ${RENDER_TIMING.expectedRange}.`
|
|
18379
|
-
].join("\n")
|
|
18640
|
+
].join("\n"),
|
|
18641
|
+
{
|
|
18642
|
+
jobs: [{ status: "rendering", job_id: job.id, composition_id: compositionId, output_format: outputFormat }]
|
|
18643
|
+
}
|
|
18380
18644
|
);
|
|
18381
18645
|
}
|
|
18382
18646
|
const result = await callApi("/internal/render", {
|
|
@@ -18385,11 +18649,21 @@ For multi-render builds (e.g. one clip per quiz question), pass everything in ON
|
|
|
18385
18649
|
});
|
|
18386
18650
|
if (!result.ok) return errorResult(result.error);
|
|
18387
18651
|
const data = result.data;
|
|
18388
|
-
return
|
|
18652
|
+
return structuredResult(
|
|
18389
18653
|
[
|
|
18390
18654
|
`Render complete. Public URL: ${data.public_url}`,
|
|
18391
18655
|
`Attach via clipform_upload_node_media with fit_media: true (composed 9:16 frame - contain, never crop).`
|
|
18392
|
-
].join("\n")
|
|
18656
|
+
].join("\n"),
|
|
18657
|
+
{
|
|
18658
|
+
jobs: [
|
|
18659
|
+
{
|
|
18660
|
+
status: "complete",
|
|
18661
|
+
...data.public_url ? { public_url: data.public_url } : {},
|
|
18662
|
+
composition_id: compositionId,
|
|
18663
|
+
output_format: outputFormat
|
|
18664
|
+
}
|
|
18665
|
+
]
|
|
18666
|
+
}
|
|
18393
18667
|
);
|
|
18394
18668
|
}
|
|
18395
18669
|
);
|
|
@@ -18410,6 +18684,19 @@ function registerSearchMusicTool(server) {
|
|
|
18410
18684
|
maxDuration: external_exports.number().optional().describe("Maximum duration in seconds"),
|
|
18411
18685
|
tags: external_exports.array(external_exports.string()).optional().describe("Genre/mood tags to filter by")
|
|
18412
18686
|
},
|
|
18687
|
+
outputSchema: {
|
|
18688
|
+
query: external_exports.string(),
|
|
18689
|
+
tracks: external_exports.array(
|
|
18690
|
+
external_exports.object({
|
|
18691
|
+
title: external_exports.string().optional(),
|
|
18692
|
+
artist: external_exports.string().optional(),
|
|
18693
|
+
url: external_exports.string(),
|
|
18694
|
+
duration: external_exports.number().optional(),
|
|
18695
|
+
source: external_exports.string().optional(),
|
|
18696
|
+
license: external_exports.string().optional()
|
|
18697
|
+
})
|
|
18698
|
+
).describe("Matching tracks (empty if none); pass a url as the audio track to generate_video")
|
|
18699
|
+
},
|
|
18413
18700
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
18414
18701
|
},
|
|
18415
18702
|
async ({ query, count, instrumentalOnly, minDuration, maxDuration, tags }) => {
|
|
@@ -18418,7 +18705,15 @@ function registerSearchMusicTool(server) {
|
|
|
18418
18705
|
});
|
|
18419
18706
|
if (!result.ok) return errorResult(result.error);
|
|
18420
18707
|
const results = result.data.results;
|
|
18421
|
-
if (!results.length) return
|
|
18708
|
+
if (!results.length) return structuredResult(`No music found for "${query}".`, { query, tracks: [] });
|
|
18709
|
+
const tracks = results.map((item) => ({
|
|
18710
|
+
...item.title ? { title: item.title } : {},
|
|
18711
|
+
...item.artist ? { artist: item.artist } : {},
|
|
18712
|
+
url: item.url,
|
|
18713
|
+
...typeof item.duration === "number" ? { duration: item.duration } : {},
|
|
18714
|
+
...item.source ? { source: item.source } : {},
|
|
18715
|
+
...item.license ? { license: item.license } : {}
|
|
18716
|
+
}));
|
|
18422
18717
|
const lines = [`Found ${results.length} tracks for "${query}":
|
|
18423
18718
|
`];
|
|
18424
18719
|
for (const item of results) {
|
|
@@ -18429,7 +18724,7 @@ function registerSearchMusicTool(server) {
|
|
|
18429
18724
|
if (item.license) lines.push(` License: ${item.license}`);
|
|
18430
18725
|
lines.push("");
|
|
18431
18726
|
}
|
|
18432
|
-
return
|
|
18727
|
+
return structuredResult(lines.join("\n"), { query, tracks });
|
|
18433
18728
|
}
|
|
18434
18729
|
);
|
|
18435
18730
|
}
|
|
@@ -18470,6 +18765,19 @@ function registerListCompositionsTool(server) {
|
|
|
18470
18765
|
title: "List Compositions",
|
|
18471
18766
|
description: `Browse available video compositions and their expected props schemas. Call this before using clipform_render_composition to discover visual styles and their input props. For narrated slideshows from images, use clipform_generate_video instead.`,
|
|
18472
18767
|
inputSchema: {},
|
|
18768
|
+
outputSchema: {
|
|
18769
|
+
compositions: external_exports.array(
|
|
18770
|
+
external_exports.object({
|
|
18771
|
+
id: external_exports.string(),
|
|
18772
|
+
width: external_exports.number(),
|
|
18773
|
+
height: external_exports.number(),
|
|
18774
|
+
fps: external_exports.number(),
|
|
18775
|
+
durationInFrames: external_exports.number(),
|
|
18776
|
+
props: external_exports.string().describe("Compact props summary (name*: type)"),
|
|
18777
|
+
propsSchema: external_exports.record(external_exports.unknown()).optional().describe("Full JSON Schema for inputProps")
|
|
18778
|
+
})
|
|
18779
|
+
).describe("Available compositions - pass an id to render_composition")
|
|
18780
|
+
},
|
|
18473
18781
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
18474
18782
|
},
|
|
18475
18783
|
async () => {
|
|
@@ -18484,7 +18792,18 @@ function registerListCompositionsTool(server) {
|
|
|
18484
18792
|
}
|
|
18485
18793
|
const all = result.data.compositions;
|
|
18486
18794
|
const compositions = all.filter((c) => EXPOSED_COMPOSITIONS.includes(c.id));
|
|
18487
|
-
if (!compositions.length) return
|
|
18795
|
+
if (!compositions.length) return structuredResult("No compositions found.", { compositions: [] });
|
|
18796
|
+
const structured = {
|
|
18797
|
+
compositions: compositions.map((comp) => ({
|
|
18798
|
+
id: comp.id,
|
|
18799
|
+
width: comp.width,
|
|
18800
|
+
height: comp.height,
|
|
18801
|
+
fps: comp.fps,
|
|
18802
|
+
durationInFrames: comp.durationInFrames,
|
|
18803
|
+
props: summarizeSchema(comp.propsSchema),
|
|
18804
|
+
...comp.propsSchema ? { propsSchema: comp.propsSchema } : {}
|
|
18805
|
+
}))
|
|
18806
|
+
};
|
|
18488
18807
|
const lines = [`Available compositions (${compositions.length}):
|
|
18489
18808
|
`];
|
|
18490
18809
|
for (const comp of compositions) {
|
|
@@ -18494,7 +18813,7 @@ function registerListCompositionsTool(server) {
|
|
|
18494
18813
|
}
|
|
18495
18814
|
lines.push("");
|
|
18496
18815
|
lines.push("Use clipform_render_composition with a compositionId to render. inputProps are validated strictly against the schema - unknown or missing props fail.");
|
|
18497
|
-
return
|
|
18816
|
+
return structuredResult(lines.join("\n"), structured);
|
|
18498
18817
|
}
|
|
18499
18818
|
);
|
|
18500
18819
|
}
|
|
@@ -18509,6 +18828,11 @@ function registerListAssetsTool(server) {
|
|
|
18509
18828
|
inputSchema: {
|
|
18510
18829
|
type: external_exports.enum(["sfx", "animation", "font", "all"]).default("all").optional().describe("Asset type to list (default: all)")
|
|
18511
18830
|
},
|
|
18831
|
+
outputSchema: {
|
|
18832
|
+
sfx: external_exports.array(external_exports.object({ name: external_exports.string(), description: external_exports.string().nullable().optional(), path: external_exports.string().optional() })),
|
|
18833
|
+
animations: external_exports.array(external_exports.object({ name: external_exports.string(), description: external_exports.string().nullable().optional(), path: external_exports.string().optional() })),
|
|
18834
|
+
fonts: external_exports.array(external_exports.object({ name: external_exports.string(), weights: external_exports.array(external_exports.union([external_exports.string(), external_exports.number()])).optional() }))
|
|
18835
|
+
},
|
|
18512
18836
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
18513
18837
|
},
|
|
18514
18838
|
async ({ type }) => {
|
|
@@ -18519,6 +18843,11 @@ function registerListAssetsTool(server) {
|
|
|
18519
18843
|
if (!result.ok) return errorResult(result.error);
|
|
18520
18844
|
const data = result.data;
|
|
18521
18845
|
const lines = [];
|
|
18846
|
+
const structured = {
|
|
18847
|
+
sfx: (data.sfx ?? []).map((s) => ({ name: s.name, description: s.description ?? null, path: s.path })),
|
|
18848
|
+
animations: (data.animations ?? []).map((a) => ({ name: a.name, description: a.description ?? null, path: a.path })),
|
|
18849
|
+
fonts: (data.fonts ?? []).map((f) => ({ name: f.name, ...Array.isArray(f.weights) ? { weights: f.weights } : {} }))
|
|
18850
|
+
};
|
|
18522
18851
|
if (data.sfx?.length) {
|
|
18523
18852
|
lines.push(`## Sound Effects (${data.sfx.length})
|
|
18524
18853
|
`);
|
|
@@ -18543,7 +18872,7 @@ function registerListAssetsTool(server) {
|
|
|
18543
18872
|
}
|
|
18544
18873
|
lines.push("");
|
|
18545
18874
|
}
|
|
18546
|
-
return
|
|
18875
|
+
return structuredResult(lines.length ? lines.join("\n") : "No assets found.", structured);
|
|
18547
18876
|
}
|
|
18548
18877
|
);
|
|
18549
18878
|
}
|
|
@@ -18594,6 +18923,12 @@ For multi-question builds, pass wait: false on every render: each call returns a
|
|
|
18594
18923
|
background_color: external_exports.string().optional().describe("Background color (default '#000')"),
|
|
18595
18924
|
wait: external_exports.boolean().optional().default(true).describe("true (default) blocks until the video is ready and returns its URL. false returns a job ID immediately - fire all renders first, then poll clipform_check_render. Use false whenever rendering more than one video.")
|
|
18596
18925
|
},
|
|
18926
|
+
outputSchema: {
|
|
18927
|
+
status: external_exports.enum(["rendering", "complete"]).describe("'rendering' when wait:false (poll check_render); 'complete' with a public_url when wait:true"),
|
|
18928
|
+
job_id: external_exports.string().optional().describe("Present when status is 'rendering' - pass to check_render"),
|
|
18929
|
+
public_url: external_exports.string().optional().describe("Present when status is 'complete' - attach via upload_node_media (fit_media: true)"),
|
|
18930
|
+
duration_seconds: external_exports.number().optional()
|
|
18931
|
+
},
|
|
18597
18932
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
18598
18933
|
},
|
|
18599
18934
|
async ({ items, audio_url, background_audio_url, background_audio_volume, duration_seconds, random_effects, transition, style_preset, texture, duotone, background_color, wait }) => {
|
|
@@ -18625,30 +18960,52 @@ For multi-question builds, pass wait: false on every render: each call returns a
|
|
|
18625
18960
|
if (r.ok) completeJob(job.id, r.data);
|
|
18626
18961
|
else failJob(job.id, r.error);
|
|
18627
18962
|
}).catch((err) => failJob(job.id, err instanceof Error ? err.message : String(err)));
|
|
18628
|
-
return
|
|
18963
|
+
return structuredResult(
|
|
18629
18964
|
[
|
|
18630
18965
|
`Render started (${items.length} item${items.length > 1 ? "s" : ""}).`,
|
|
18631
18966
|
`Job ID: ${job.id}`,
|
|
18632
18967
|
`Fire any remaining renders now, then poll clipform_check_render. Renders typically take ${RENDER_TIMING.expectedRange}.`
|
|
18633
|
-
].join("\n")
|
|
18968
|
+
].join("\n"),
|
|
18969
|
+
{ status: "rendering", job_id: job.id }
|
|
18634
18970
|
);
|
|
18635
18971
|
}
|
|
18636
18972
|
const result = await apiCall();
|
|
18637
18973
|
if (!result.ok) return errorResult(result.error);
|
|
18638
18974
|
const data = result.data;
|
|
18639
|
-
return
|
|
18975
|
+
return structuredResult(
|
|
18640
18976
|
[
|
|
18641
18977
|
`Video rendered (${items.length} item${items.length > 1 ? "s" : ""}).`,
|
|
18642
18978
|
`Public URL: ${data.public_url}`,
|
|
18643
18979
|
data.duration_seconds ? `Duration: ${data.duration_seconds}s` : "",
|
|
18644
18980
|
`Attach via clipform_upload_node_media with fit_media: true (composed 9:16 frame - contain, never crop).`
|
|
18645
|
-
].filter(Boolean).join("\n")
|
|
18981
|
+
].filter(Boolean).join("\n"),
|
|
18982
|
+
{
|
|
18983
|
+
status: "complete",
|
|
18984
|
+
...data.public_url ? { public_url: data.public_url } : {},
|
|
18985
|
+
...typeof data.duration_seconds === "number" ? { duration_seconds: data.duration_seconds } : {}
|
|
18986
|
+
}
|
|
18646
18987
|
);
|
|
18647
18988
|
}
|
|
18648
18989
|
);
|
|
18649
18990
|
}
|
|
18650
18991
|
|
|
18651
18992
|
// src/tools/check-render.ts
|
|
18993
|
+
function projectJob(id, job) {
|
|
18994
|
+
if (!job) return { job_id: id, status: "not_found" };
|
|
18995
|
+
if (job.status === "rendering") {
|
|
18996
|
+
return { job_id: id, status: "rendering", elapsed_seconds: Math.round((Date.now() - job.createdAt) / 1e3) };
|
|
18997
|
+
}
|
|
18998
|
+
if (job.status === "failed") {
|
|
18999
|
+
return { job_id: id, status: "failed", ...job.error ? { error: job.error } : {} };
|
|
19000
|
+
}
|
|
19001
|
+
const data = job.result;
|
|
19002
|
+
return {
|
|
19003
|
+
job_id: id,
|
|
19004
|
+
status: "complete",
|
|
19005
|
+
...data?.public_url ? { public_url: data.public_url } : {},
|
|
19006
|
+
...typeof data?.duration_seconds === "number" ? { duration_seconds: data.duration_seconds } : {}
|
|
19007
|
+
};
|
|
19008
|
+
}
|
|
18652
19009
|
var LOST_JOB_RECOVERY = [
|
|
18653
19010
|
`Jobs expire after 30 minutes and are lost if the server restarts - the render itself may still have completed.`,
|
|
18654
19011
|
`To recover, re-run the original render call with identical arguments:`,
|
|
@@ -18685,6 +19042,18 @@ Pass job_ids to check a whole batch in ONE call - one line of status per job. Pa
|
|
|
18685
19042
|
job_id: external_exports.string().uuid().optional().describe("A single job ID returned by the render tool"),
|
|
18686
19043
|
job_ids: external_exports.array(external_exports.string().uuid()).min(1).max(20).optional().describe("Multiple job IDs - check the whole batch in one call instead of one call per job")
|
|
18687
19044
|
},
|
|
19045
|
+
outputSchema: {
|
|
19046
|
+
jobs: external_exports.array(
|
|
19047
|
+
external_exports.object({
|
|
19048
|
+
job_id: external_exports.string(),
|
|
19049
|
+
status: external_exports.enum(["rendering", "complete", "failed", "not_found"]),
|
|
19050
|
+
public_url: external_exports.string().optional(),
|
|
19051
|
+
duration_seconds: external_exports.number().optional(),
|
|
19052
|
+
elapsed_seconds: external_exports.number().optional(),
|
|
19053
|
+
error: external_exports.string().optional()
|
|
19054
|
+
})
|
|
19055
|
+
).describe("One entry per queried job; attach completed public_urls via upload_node_media")
|
|
19056
|
+
},
|
|
18688
19057
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
18689
19058
|
},
|
|
18690
19059
|
async ({ job_id, job_ids }) => {
|
|
@@ -18700,38 +19069,41 @@ Pass job_ids to check a whole batch in ONE call - one line of status per job. Pa
|
|
|
18700
19069
|
}
|
|
18701
19070
|
if (job.status === "rendering") {
|
|
18702
19071
|
const elapsed = Math.round((Date.now() - job.createdAt) / 1e3);
|
|
18703
|
-
return
|
|
19072
|
+
return structuredResult(
|
|
18704
19073
|
[
|
|
18705
19074
|
`Status: rendering (${elapsed}s elapsed)`,
|
|
18706
19075
|
`Tool: ${job.tool}`,
|
|
18707
19076
|
``,
|
|
18708
19077
|
`Still in progress. Check again in ${RENDER_TIMING.pollDelay}.`
|
|
18709
|
-
].join("\n")
|
|
19078
|
+
].join("\n"),
|
|
19079
|
+
{ jobs: [projectJob(ids[0], job)] }
|
|
18710
19080
|
);
|
|
18711
19081
|
}
|
|
18712
19082
|
if (job.status === "failed") {
|
|
18713
19083
|
return errorResult(`Render failed: ${job.error}`);
|
|
18714
19084
|
}
|
|
18715
19085
|
const data = job.result;
|
|
18716
|
-
return
|
|
19086
|
+
return structuredResult(
|
|
18717
19087
|
[
|
|
18718
19088
|
`Render complete.`,
|
|
18719
19089
|
...data.public_url ? [`Public URL: ${data.public_url}`] : [],
|
|
18720
19090
|
...data.duration_seconds ? [`Duration: ${data.duration_seconds}s`] : [],
|
|
18721
19091
|
`Attach via clipform_upload_node_media with fit_media: true (composed 9:16 frame - contain, never crop).`
|
|
18722
|
-
].join("\n")
|
|
19092
|
+
].join("\n"),
|
|
19093
|
+
{ jobs: [projectJob(ids[0], job)] }
|
|
18723
19094
|
);
|
|
18724
19095
|
}
|
|
18725
19096
|
const results = ids.map((id) => describeJob(id, getJob(id)));
|
|
18726
19097
|
const pending = results.filter((r) => !r.done).length;
|
|
18727
19098
|
const lost = results.some((r) => r.line.includes("NOT FOUND"));
|
|
18728
|
-
return
|
|
19099
|
+
return structuredResult(
|
|
18729
19100
|
[
|
|
18730
19101
|
`Render batch: ${results.length - pending}/${results.length} finished${pending ? ` - check again in ${RENDER_TIMING.pollDelay}` : ""}.`,
|
|
18731
19102
|
...results.map((r) => r.line),
|
|
18732
19103
|
...pending === 0 ? [``, `Attach completed renders via clipform_upload_node_media with fit_media: true (composed 9:16 frames - contain, never crop).`] : [],
|
|
18733
19104
|
...lost ? [``, LOST_JOB_RECOVERY] : []
|
|
18734
|
-
].join("\n")
|
|
19105
|
+
].join("\n"),
|
|
19106
|
+
{ jobs: ids.map((id) => projectJob(id, getJob(id))) }
|
|
18735
19107
|
);
|
|
18736
19108
|
}
|
|
18737
19109
|
);
|
|
@@ -19047,4 +19419,4 @@ export {
|
|
|
19047
19419
|
JSONRPCMessageSchema,
|
|
19048
19420
|
createServer
|
|
19049
19421
|
};
|
|
19050
|
-
//# sourceMappingURL=chunk-
|
|
19422
|
+
//# sourceMappingURL=chunk-3MTEM3VC.js.map
|