@clipform/mcp-server 2.18.0 → 3.2.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 +4 -5
- package/README.template.md +1 -1
- package/dist/auth-context.d.ts +13 -0
- package/dist/auth-context.js +1 -1
- package/dist/{chunk-CHSPV2VI.js → chunk-236IGTEY.js} +2 -2
- package/dist/{chunk-HZMMOSEM.js → chunk-A5CFGL27.js} +1 -1
- package/dist/chunk-A5CFGL27.js.map +1 -0
- package/dist/{chunk-WWIQEIFF.js → chunk-EQ6WNIA5.js} +10 -10
- package/dist/{chunk-WWIQEIFF.js.map → chunk-EQ6WNIA5.js.map} +1 -1
- package/dist/{chunk-Y4RGN6KA.js → chunk-J7727374.js} +327 -274
- package/dist/chunk-J7727374.js.map +1 -0
- package/dist/{chunk-RKYB6247.js → chunk-VZKMOMFJ.js} +29 -284
- package/dist/chunk-VZKMOMFJ.js.map +1 -0
- package/dist/index.js +5 -5
- package/dist/prompts.js +3 -3
- package/dist/resources.js +3 -3
- package/dist/server.js +5 -5
- package/package.json +4 -3
- package/dist/chunk-HZMMOSEM.js.map +0 -1
- package/dist/chunk-RKYB6247.js.map +0 -1
- package/dist/chunk-Y4RGN6KA.js.map +0 -1
- /package/dist/{chunk-CHSPV2VI.js.map → chunk-236IGTEY.js.map} +0 -0
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
getWorkflowText,
|
|
7
7
|
objectType,
|
|
8
8
|
registerPrompts
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-EQ6WNIA5.js";
|
|
10
10
|
import {
|
|
11
11
|
GUIDE_TYPES,
|
|
12
12
|
QUIZ_VARIANTS,
|
|
@@ -15,39 +15,32 @@ import {
|
|
|
15
15
|
getGuideUri,
|
|
16
16
|
guideFallbackText,
|
|
17
17
|
registerResources
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-236IGTEY.js";
|
|
19
19
|
import {
|
|
20
20
|
ACTIVE_NODE_TYPES,
|
|
21
21
|
CONTACT_FIELDS,
|
|
22
22
|
FEATURE_FLAGS,
|
|
23
23
|
FORM_TYPE_ALIASES,
|
|
24
24
|
FULL_CAPS,
|
|
25
|
-
HTTP_URL_MESSAGE,
|
|
26
|
-
KEN_BURNS_EFFECTS,
|
|
27
|
-
KEN_BURNS_FIT_MODES,
|
|
28
|
-
KEN_BURNS_PRESETS,
|
|
29
25
|
MCP_TOOL_TIERS,
|
|
30
26
|
MCP_TOOL_TOOLSETS,
|
|
31
27
|
NODE_TYPES,
|
|
32
28
|
NON_COUNTABLE_TYPES,
|
|
33
|
-
SLIDESHOW_COMPOSITION_TRANSITIONS,
|
|
34
|
-
SLIDESHOW_TRANSITION_DURATION,
|
|
35
|
-
VIDEO_LOOK_NAMES,
|
|
36
29
|
bfsOrder,
|
|
37
30
|
callApi,
|
|
38
31
|
capsFromToolsets,
|
|
39
32
|
defaultTarget,
|
|
40
33
|
errorResult,
|
|
41
34
|
exposedCompositionIds,
|
|
42
|
-
isHttpUrl,
|
|
43
35
|
resolveFormType,
|
|
44
36
|
structuredResult,
|
|
45
37
|
textResult
|
|
46
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-VZKMOMFJ.js";
|
|
47
39
|
import {
|
|
48
40
|
getMcpAuth,
|
|
41
|
+
runWithMcpAuth,
|
|
49
42
|
runWithMcpTool
|
|
50
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-A5CFGL27.js";
|
|
51
44
|
import {
|
|
52
45
|
__commonJS,
|
|
53
46
|
__export,
|
|
@@ -122,7 +115,7 @@ var require_code = __commonJS({
|
|
|
122
115
|
}
|
|
123
116
|
exports._ = _;
|
|
124
117
|
var plus = new _Code("+");
|
|
125
|
-
function
|
|
118
|
+
function str2(strs, ...args) {
|
|
126
119
|
const expr = [safeStringify(strs[0])];
|
|
127
120
|
let i = 0;
|
|
128
121
|
while (i < args.length) {
|
|
@@ -133,7 +126,7 @@ var require_code = __commonJS({
|
|
|
133
126
|
optimize(expr);
|
|
134
127
|
return new _Code(expr);
|
|
135
128
|
}
|
|
136
|
-
exports.str =
|
|
129
|
+
exports.str = str2;
|
|
137
130
|
function addCodeArg(code, arg) {
|
|
138
131
|
if (arg instanceof _Code)
|
|
139
132
|
code.push(...arg._items);
|
|
@@ -176,7 +169,7 @@ var require_code = __commonJS({
|
|
|
176
169
|
return;
|
|
177
170
|
}
|
|
178
171
|
function strConcat(c1, c2) {
|
|
179
|
-
return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 :
|
|
172
|
+
return c2.emptyStr() ? c1 : c1.emptyStr() ? c2 : str2`${c1}${c2}`;
|
|
180
173
|
}
|
|
181
174
|
exports.strConcat = strConcat;
|
|
182
175
|
function interpolate(x) {
|
|
@@ -1138,22 +1131,22 @@ var require_util = __commonJS({
|
|
|
1138
1131
|
return (0, codegen_1._)`${topSchemaRef}${schemaPath}${(0, codegen_1.getProperty)(keyword)}`;
|
|
1139
1132
|
}
|
|
1140
1133
|
exports.schemaRefOrVal = schemaRefOrVal;
|
|
1141
|
-
function unescapeFragment(
|
|
1142
|
-
return unescapeJsonPointer(decodeURIComponent(
|
|
1134
|
+
function unescapeFragment(str2) {
|
|
1135
|
+
return unescapeJsonPointer(decodeURIComponent(str2));
|
|
1143
1136
|
}
|
|
1144
1137
|
exports.unescapeFragment = unescapeFragment;
|
|
1145
|
-
function escapeFragment(
|
|
1146
|
-
return encodeURIComponent(escapeJsonPointer(
|
|
1138
|
+
function escapeFragment(str2) {
|
|
1139
|
+
return encodeURIComponent(escapeJsonPointer(str2));
|
|
1147
1140
|
}
|
|
1148
1141
|
exports.escapeFragment = escapeFragment;
|
|
1149
|
-
function escapeJsonPointer(
|
|
1150
|
-
if (typeof
|
|
1151
|
-
return `${
|
|
1152
|
-
return
|
|
1142
|
+
function escapeJsonPointer(str2) {
|
|
1143
|
+
if (typeof str2 == "number")
|
|
1144
|
+
return `${str2}`;
|
|
1145
|
+
return str2.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
1153
1146
|
}
|
|
1154
1147
|
exports.escapeJsonPointer = escapeJsonPointer;
|
|
1155
|
-
function unescapeJsonPointer(
|
|
1156
|
-
return
|
|
1148
|
+
function unescapeJsonPointer(str2) {
|
|
1149
|
+
return str2.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
1157
1150
|
}
|
|
1158
1151
|
exports.unescapeJsonPointer = unescapeJsonPointer;
|
|
1159
1152
|
function eachItem(xs, f) {
|
|
@@ -2178,8 +2171,8 @@ var require_json_schema_traverse = __commonJS({
|
|
|
2178
2171
|
post(schema, jsonPtr, rootSchema, parentJsonPtr, parentKeyword, parentSchema, keyIndex);
|
|
2179
2172
|
}
|
|
2180
2173
|
}
|
|
2181
|
-
function escapeJsonPtr(
|
|
2182
|
-
return
|
|
2174
|
+
function escapeJsonPtr(str2) {
|
|
2175
|
+
return str2.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
2183
2176
|
}
|
|
2184
2177
|
}
|
|
2185
2178
|
});
|
|
@@ -3243,10 +3236,10 @@ var require_utils = __commonJS({
|
|
|
3243
3236
|
return { host, isIPV6: false };
|
|
3244
3237
|
}
|
|
3245
3238
|
}
|
|
3246
|
-
function findToken(
|
|
3239
|
+
function findToken(str2, token) {
|
|
3247
3240
|
let ind = 0;
|
|
3248
|
-
for (let i = 0; i <
|
|
3249
|
-
if (
|
|
3241
|
+
for (let i = 0; i < str2.length; i++) {
|
|
3242
|
+
if (str2[i] === token) ind++;
|
|
3250
3243
|
}
|
|
3251
3244
|
return ind;
|
|
3252
3245
|
}
|
|
@@ -3983,7 +3976,7 @@ var require_core = __commonJS({
|
|
|
3983
3976
|
var util_1 = require_util();
|
|
3984
3977
|
var $dataRefSchema = require_data();
|
|
3985
3978
|
var uri_1 = require_uri();
|
|
3986
|
-
var defaultRegExp = (
|
|
3979
|
+
var defaultRegExp = (str2, flags) => new RegExp(str2, flags);
|
|
3987
3980
|
defaultRegExp.code = "new RegExp";
|
|
3988
3981
|
var META_IGNORE_OPTIONS = ["removeAdditional", "useDefaults", "coerceTypes"];
|
|
3989
3982
|
var EXT_SCOPE_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -4778,16 +4771,16 @@ var require_ucs2length = __commonJS({
|
|
|
4778
4771
|
"../../node_modules/.pnpm/ajv@8.20.0/node_modules/ajv/dist/runtime/ucs2length.js"(exports) {
|
|
4779
4772
|
"use strict";
|
|
4780
4773
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4781
|
-
function ucs2length(
|
|
4782
|
-
const len =
|
|
4774
|
+
function ucs2length(str2) {
|
|
4775
|
+
const len = str2.length;
|
|
4783
4776
|
let length = 0;
|
|
4784
4777
|
let pos = 0;
|
|
4785
4778
|
let value;
|
|
4786
4779
|
while (pos < len) {
|
|
4787
4780
|
length++;
|
|
4788
|
-
value =
|
|
4781
|
+
value = str2.charCodeAt(pos++);
|
|
4789
4782
|
if (value >= 55296 && value <= 56319 && pos < len) {
|
|
4790
|
-
value =
|
|
4783
|
+
value = str2.charCodeAt(pos);
|
|
4791
4784
|
if ((value & 64512) === 56320)
|
|
4792
4785
|
pos++;
|
|
4793
4786
|
}
|
|
@@ -6670,8 +6663,8 @@ var require_formats = __commonJS({
|
|
|
6670
6663
|
}
|
|
6671
6664
|
var DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
|
|
6672
6665
|
var DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
6673
|
-
function date3(
|
|
6674
|
-
const matches = DATE.exec(
|
|
6666
|
+
function date3(str2) {
|
|
6667
|
+
const matches = DATE.exec(str2);
|
|
6675
6668
|
if (!matches)
|
|
6676
6669
|
return false;
|
|
6677
6670
|
const year = +matches[1];
|
|
@@ -6690,8 +6683,8 @@ var require_formats = __commonJS({
|
|
|
6690
6683
|
}
|
|
6691
6684
|
var TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i;
|
|
6692
6685
|
function getTime(strictTimeZone) {
|
|
6693
|
-
return function time3(
|
|
6694
|
-
const matches = TIME.exec(
|
|
6686
|
+
return function time3(str2) {
|
|
6687
|
+
const matches = TIME.exec(str2);
|
|
6695
6688
|
if (!matches)
|
|
6696
6689
|
return false;
|
|
6697
6690
|
const hr = +matches[1];
|
|
@@ -6737,8 +6730,8 @@ var require_formats = __commonJS({
|
|
|
6737
6730
|
var DATE_TIME_SEPARATOR = /t|\s/i;
|
|
6738
6731
|
function getDateTime(strictTimeZone) {
|
|
6739
6732
|
const time3 = getTime(strictTimeZone);
|
|
6740
|
-
return function date_time(
|
|
6741
|
-
const dateTime =
|
|
6733
|
+
return function date_time(str2) {
|
|
6734
|
+
const dateTime = str2.split(DATE_TIME_SEPARATOR);
|
|
6742
6735
|
return dateTime.length === 2 && date3(dateTime[0]) && time3(dateTime[1]);
|
|
6743
6736
|
};
|
|
6744
6737
|
}
|
|
@@ -6763,13 +6756,13 @@ var require_formats = __commonJS({
|
|
|
6763
6756
|
}
|
|
6764
6757
|
var NOT_URI_FRAGMENT = /\/|:/;
|
|
6765
6758
|
var URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
|
|
6766
|
-
function uri(
|
|
6767
|
-
return NOT_URI_FRAGMENT.test(
|
|
6759
|
+
function uri(str2) {
|
|
6760
|
+
return NOT_URI_FRAGMENT.test(str2) && URI.test(str2);
|
|
6768
6761
|
}
|
|
6769
6762
|
var BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm;
|
|
6770
|
-
function byte(
|
|
6763
|
+
function byte(str2) {
|
|
6771
6764
|
BYTE.lastIndex = 0;
|
|
6772
|
-
return BYTE.test(
|
|
6765
|
+
return BYTE.test(str2);
|
|
6773
6766
|
}
|
|
6774
6767
|
var MIN_INT32 = -(2 ** 31);
|
|
6775
6768
|
var MAX_INT32 = 2 ** 31 - 1;
|
|
@@ -6783,11 +6776,11 @@ var require_formats = __commonJS({
|
|
|
6783
6776
|
return true;
|
|
6784
6777
|
}
|
|
6785
6778
|
var Z_ANCHOR = /[^\\]\\Z/;
|
|
6786
|
-
function regex(
|
|
6787
|
-
if (Z_ANCHOR.test(
|
|
6779
|
+
function regex(str2) {
|
|
6780
|
+
if (Z_ANCHOR.test(str2))
|
|
6788
6781
|
return false;
|
|
6789
6782
|
try {
|
|
6790
|
-
new RegExp(
|
|
6783
|
+
new RegExp(str2);
|
|
6791
6784
|
return true;
|
|
6792
6785
|
} catch (e) {
|
|
6793
6786
|
return false;
|
|
@@ -7123,14 +7116,14 @@ function promiseAllObject(promisesObj) {
|
|
|
7123
7116
|
}
|
|
7124
7117
|
function randomString(length = 10) {
|
|
7125
7118
|
const chars = "abcdefghijklmnopqrstuvwxyz";
|
|
7126
|
-
let
|
|
7119
|
+
let str2 = "";
|
|
7127
7120
|
for (let i = 0; i < length; i++) {
|
|
7128
|
-
|
|
7121
|
+
str2 += chars[Math.floor(Math.random() * chars.length)];
|
|
7129
7122
|
}
|
|
7130
|
-
return
|
|
7123
|
+
return str2;
|
|
7131
7124
|
}
|
|
7132
|
-
function esc(
|
|
7133
|
-
return JSON.stringify(
|
|
7125
|
+
function esc(str2) {
|
|
7126
|
+
return JSON.stringify(str2);
|
|
7134
7127
|
}
|
|
7135
7128
|
var captureStackTrace = Error.captureStackTrace ? Error.captureStackTrace : (..._args) => {
|
|
7136
7129
|
};
|
|
@@ -7218,8 +7211,8 @@ var getParsedType = (data) => {
|
|
|
7218
7211
|
};
|
|
7219
7212
|
var propertyKeyTypes = /* @__PURE__ */ new Set(["string", "number", "symbol"]);
|
|
7220
7213
|
var primitiveTypes = /* @__PURE__ */ new Set(["string", "number", "bigint", "boolean", "symbol", "undefined"]);
|
|
7221
|
-
function escapeRegex(
|
|
7222
|
-
return
|
|
7214
|
+
function escapeRegex(str2) {
|
|
7215
|
+
return str2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7223
7216
|
}
|
|
7224
7217
|
function clone(inst, def, params) {
|
|
7225
7218
|
const cl = new inst._zod.constr(def ?? inst._zod.def);
|
|
@@ -17111,7 +17104,6 @@ var NODE_TYPES_DESCRIPTION = (() => {
|
|
|
17111
17104
|
return lines.join("\n");
|
|
17112
17105
|
})();
|
|
17113
17106
|
var CONFIG_FIELD_DESCRIPTION = "Type-specific configuration keyed by node type. Omit to use defaults. Per-type keys, shapes, and defaults are listed in the clipform_create_form description; craft guidance via clipform_get_guide.";
|
|
17114
|
-
var STYLE_PRESET_ENUM = external_exports.enum(KEN_BURNS_PRESETS).describe(`Ken Burns style preset: ${KEN_BURNS_PRESETS.join(", ")}`);
|
|
17115
17107
|
var CaptionSchema = external_exports.object({
|
|
17116
17108
|
start: external_exports.number().describe("Segment start time in seconds"),
|
|
17117
17109
|
end: external_exports.number().describe("Segment end time in seconds"),
|
|
@@ -17196,6 +17188,48 @@ function applyNodeDefaults(node) {
|
|
|
17196
17188
|
}
|
|
17197
17189
|
}
|
|
17198
17190
|
|
|
17191
|
+
// src/lib/workspace-target.ts
|
|
17192
|
+
var PINNED_WORKSPACE_ERROR = "This connection is pinned to one workspace, so a workspace argument can't be used. Omit it to act in the connection's workspace.";
|
|
17193
|
+
async function runWithWorkspaceTarget(args, run) {
|
|
17194
|
+
const input = args && typeof args === "object" ? args : {};
|
|
17195
|
+
const workspaceId = typeof input.workspace_id === "string" && input.workspace_id ? input.workspace_id : void 0;
|
|
17196
|
+
const formId = typeof input.form_id === "string" && input.form_id ? input.form_id : void 0;
|
|
17197
|
+
if (!workspaceId && !formId) return run();
|
|
17198
|
+
const ctx = getMcpAuth();
|
|
17199
|
+
const resolve = ctx?.resolveWorkspace;
|
|
17200
|
+
if (!resolve) {
|
|
17201
|
+
if (workspaceId) return errorResult(PINNED_WORKSPACE_ERROR);
|
|
17202
|
+
return run();
|
|
17203
|
+
}
|
|
17204
|
+
if (workspaceId && !formId && workspaceId === ctx.default_workspace_id) {
|
|
17205
|
+
return run();
|
|
17206
|
+
}
|
|
17207
|
+
let resolved;
|
|
17208
|
+
try {
|
|
17209
|
+
resolved = await resolve({
|
|
17210
|
+
...workspaceId ? { workspace_id: workspaceId } : {},
|
|
17211
|
+
...formId ? { form_id: formId } : {}
|
|
17212
|
+
});
|
|
17213
|
+
} catch (err) {
|
|
17214
|
+
return errorResult(err instanceof Error ? err.message : "Could not resolve that workspace.");
|
|
17215
|
+
}
|
|
17216
|
+
if (!resolved) return run();
|
|
17217
|
+
return runWithMcpAuth(
|
|
17218
|
+
{
|
|
17219
|
+
...ctx,
|
|
17220
|
+
workspace_id: resolved.workspace_id,
|
|
17221
|
+
api_key: resolved.api_key
|
|
17222
|
+
},
|
|
17223
|
+
run
|
|
17224
|
+
);
|
|
17225
|
+
}
|
|
17226
|
+
var workspaceIdInput = external_exports.string().uuid().optional().describe(
|
|
17227
|
+
"Workspace to act in (id from clipform_whoami workspaces). Omit to use the connection's default workspace."
|
|
17228
|
+
);
|
|
17229
|
+
var formIdTargetInput = external_exports.string().uuid().optional().describe(
|
|
17230
|
+
"The form this is for (from clipform_create_form) - a routing hint only, to pick the right workspace. The result still lands in the workspace library, not on a node. Required on an anonymous session, or this lands in a different workspace and clipform_attach_node_media will not find it."
|
|
17231
|
+
);
|
|
17232
|
+
|
|
17199
17233
|
// src/tools/create-form.ts
|
|
17200
17234
|
function registerCreateFormTool(server) {
|
|
17201
17235
|
server.registerTool(
|
|
@@ -17220,6 +17254,7 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
17220
17254
|
]
|
|
17221
17255
|
}`,
|
|
17222
17256
|
inputSchema: {
|
|
17257
|
+
workspace_id: workspaceIdInput,
|
|
17223
17258
|
title: external_exports.string().describe("Form title"),
|
|
17224
17259
|
nodes: external_exports.array(NodeSchema).min(1).describe("Ordered list of nodes/steps for the form"),
|
|
17225
17260
|
show_step_counter: external_exports.boolean().optional().describe("Show step counter (e.g. '1/5'). Set true for quizzes."),
|
|
@@ -17238,7 +17273,8 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
17238
17273
|
viewer_url: external_exports.string().describe("Form URL for respondents - a live public URL once authenticated/published, or a scoped preview-only link for an anonymous session"),
|
|
17239
17274
|
dashboard_url: external_exports.string().optional().describe("Builder URL (sign in to edit)"),
|
|
17240
17275
|
nodes: external_exports.array(external_exports.object({ id: external_exports.string(), type: external_exports.string(), prompt: external_exports.string() })).describe("Created nodes in order - IDs for attach_node_media / update_node"),
|
|
17241
|
-
plan: external_exports.object({ name: external_exports.string(), auth_mode: external_exports.string() }).optional().describe("Plan and auth context for this session")
|
|
17276
|
+
plan: external_exports.object({ name: external_exports.string(), auth_mode: external_exports.string() }).optional().describe("Plan and auth context for this session"),
|
|
17277
|
+
workspace_name: external_exports.string().optional().describe("Name of the workspace the form was created in")
|
|
17242
17278
|
},
|
|
17243
17279
|
annotations: {
|
|
17244
17280
|
readOnlyHint: false,
|
|
@@ -17351,6 +17387,7 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
17351
17387
|
isPreview ? `Form created as a preview draft.` : `Form created and live.`,
|
|
17352
17388
|
``,
|
|
17353
17389
|
`Title: ${title}`,
|
|
17390
|
+
...planContext?.is_authenticated && planContext.workspace_name ? [`Workspace: ${planContext.workspace_name}`] : [],
|
|
17354
17391
|
`Form ID: ${formId}`,
|
|
17355
17392
|
``,
|
|
17356
17393
|
`Node IDs (use directly with attach_node_media / update_node - no get_form round-trip needed):`,
|
|
@@ -17398,15 +17435,111 @@ Example: A form that asks a question, collects contact info, then finishes:
|
|
|
17398
17435
|
// runtime. structuredContent should return only relevant data (ChatGPT
|
|
17399
17436
|
// App Directory response-hygiene policy, #797).
|
|
17400
17437
|
nodes: createdNodes.map((n) => ({ id: n.id, type: n.type, prompt: n.prompt })),
|
|
17401
|
-
...planContext ? { plan: { name: planContext.plan_name, auth_mode: planContext.auth_mode } } : {}
|
|
17438
|
+
...planContext ? { plan: { name: planContext.plan_name, auth_mode: planContext.auth_mode } } : {},
|
|
17439
|
+
...planContext?.is_authenticated && planContext.workspace_name ? { workspace_name: planContext.workspace_name } : {}
|
|
17402
17440
|
}
|
|
17403
17441
|
};
|
|
17404
17442
|
}
|
|
17405
17443
|
);
|
|
17406
17444
|
}
|
|
17407
17445
|
|
|
17408
|
-
//
|
|
17409
|
-
|
|
17446
|
+
// ../form-sources/dist/types.js
|
|
17447
|
+
var isObj = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
17448
|
+
var str = (v) => typeof v === "string" ? v : "";
|
|
17449
|
+
var DEFAULT_QUESTION_CAP = 50;
|
|
17450
|
+
var MAX_OPTIONS = 10;
|
|
17451
|
+
|
|
17452
|
+
// ../form-sources/dist/patterns.js
|
|
17453
|
+
var firstMatch = (url, patterns) => {
|
|
17454
|
+
for (const re of patterns) {
|
|
17455
|
+
const id = re.exec(url)?.[1];
|
|
17456
|
+
if (id)
|
|
17457
|
+
return id;
|
|
17458
|
+
}
|
|
17459
|
+
return null;
|
|
17460
|
+
};
|
|
17461
|
+
var VIDEOASK_PATTERNS = [
|
|
17462
|
+
/^https?:\/\/api\.videoask\.com\/forms\/sharing\/(f[a-z0-9]{8})(?:[/?#]|$)/i,
|
|
17463
|
+
/^https?:\/\/(?:www\.)?videoask\.com\/(f[a-z0-9]{8})(?:[/?#]|$)/i
|
|
17464
|
+
];
|
|
17465
|
+
var detectVideoAskId = (url) => firstMatch(url, VIDEOASK_PATTERNS);
|
|
17466
|
+
var TYPEFORM_ATTR_KINDS = {
|
|
17467
|
+
"data-tf-widget": "inline",
|
|
17468
|
+
"data-tf-popup": "popup",
|
|
17469
|
+
"data-tf-slider": "slider",
|
|
17470
|
+
"data-tf-sidetab": "slider",
|
|
17471
|
+
"data-tf-popover": "popover"
|
|
17472
|
+
};
|
|
17473
|
+
var TALLY_ATTR_KINDS = {
|
|
17474
|
+
"data-tally-src": "inline",
|
|
17475
|
+
"data-tally-open": "popup"
|
|
17476
|
+
};
|
|
17477
|
+
var EMBED_ATTR_NAMES = [...Object.keys(TYPEFORM_ATTR_KINDS), ...Object.keys(TALLY_ATTR_KINDS)];
|
|
17478
|
+
function pushUnique(out, ref) {
|
|
17479
|
+
if (!out.some((r) => r.id === ref.id))
|
|
17480
|
+
out.push(ref);
|
|
17481
|
+
}
|
|
17482
|
+
function detectVideoAskScript(text) {
|
|
17483
|
+
const out = [];
|
|
17484
|
+
if (!/VIDEOASK_EMBED_CONFIG/.test(text))
|
|
17485
|
+
return out;
|
|
17486
|
+
for (const m of text.matchAll(/videoask\.com\/(f[a-z0-9]{8})(?![a-z0-9])/gi))
|
|
17487
|
+
pushUnique(out, { id: m[1].toLowerCase() });
|
|
17488
|
+
return out;
|
|
17489
|
+
}
|
|
17490
|
+
|
|
17491
|
+
// ../form-sources/dist/providers/videoask.js
|
|
17492
|
+
function optionText(o) {
|
|
17493
|
+
if (typeof o === "string")
|
|
17494
|
+
return o;
|
|
17495
|
+
if (isObj(o))
|
|
17496
|
+
return str(o.label) || str(o.text) || str(o.value) || str(o.title);
|
|
17497
|
+
return "";
|
|
17498
|
+
}
|
|
17499
|
+
var videoask = {
|
|
17500
|
+
id: "videoask",
|
|
17501
|
+
detect: detectVideoAskId,
|
|
17502
|
+
detectScript: detectVideoAskScript,
|
|
17503
|
+
fetchSpec: (id) => ({ url: `https://api.videoask.com/forms/sharing/${id}`, responseType: "json" }),
|
|
17504
|
+
/**
|
|
17505
|
+
* `GET api.videoask.com/forms/sharing/<id>`. Personal/account fields
|
|
17506
|
+
* (`author_id`, `organization_id`, ...) are dropped by construction: only the
|
|
17507
|
+
* fields below are copied.
|
|
17508
|
+
*/
|
|
17509
|
+
summarize(id, json, opts) {
|
|
17510
|
+
if (!isObj(json) || !Array.isArray(json.questions))
|
|
17511
|
+
throw new Error("not a VideoAsk form payload");
|
|
17512
|
+
const transcriptCap = opts?.transcriptCap;
|
|
17513
|
+
const questions = json.questions.slice(0, opts?.questionCap ?? DEFAULT_QUESTION_CAP).map((q) => {
|
|
17514
|
+
const o = isObj(q) ? q : {};
|
|
17515
|
+
const transcript = str(o.transcription);
|
|
17516
|
+
return {
|
|
17517
|
+
label: str(o.label),
|
|
17518
|
+
type: str(o.type),
|
|
17519
|
+
options: (Array.isArray(o.options) ? o.options : []).map(optionText).filter(Boolean).slice(0, MAX_OPTIONS),
|
|
17520
|
+
transcript: transcriptCap === void 0 ? transcript : transcript.slice(0, transcriptCap)
|
|
17521
|
+
};
|
|
17522
|
+
});
|
|
17523
|
+
return {
|
|
17524
|
+
provider: "videoask",
|
|
17525
|
+
id,
|
|
17526
|
+
title: str(json.title),
|
|
17527
|
+
questions,
|
|
17528
|
+
meta: {
|
|
17529
|
+
...str(json.created_at) ? { created_at: str(json.created_at) } : {},
|
|
17530
|
+
...str(json.updated_at) ? { last_edited_at: str(json.updated_at) } : {},
|
|
17531
|
+
// hide_branding is a paid-plan feature.
|
|
17532
|
+
paid_plan: json.hide_branding === true,
|
|
17533
|
+
asks_contact: ["requires_contact_email", "requires_contact_info", "requires_contact_name", "requires_contact_phone_number"].some((k) => json[k] === true),
|
|
17534
|
+
...str(json.status) ? { status: str(json.status) } : {}
|
|
17535
|
+
}
|
|
17536
|
+
};
|
|
17537
|
+
}
|
|
17538
|
+
};
|
|
17539
|
+
|
|
17540
|
+
// ../form-sources/dist/index.js
|
|
17541
|
+
var IMPORTABLE_PROVIDERS = ["google", "typeform", "tally"];
|
|
17542
|
+
function detectProvider(url) {
|
|
17410
17543
|
let parsed;
|
|
17411
17544
|
try {
|
|
17412
17545
|
parsed = new URL(url);
|
|
@@ -17414,17 +17547,22 @@ function detectImportPlatform(url) {
|
|
|
17414
17547
|
return null;
|
|
17415
17548
|
}
|
|
17416
17549
|
const hostname2 = parsed.hostname.toLowerCase();
|
|
17417
|
-
if (hostname2 === "docs.google.com" || hostname2 === "forms.gle")
|
|
17550
|
+
if (hostname2 === "docs.google.com" || hostname2 === "forms.gle")
|
|
17418
17551
|
return "google";
|
|
17419
|
-
|
|
17420
|
-
if (hostname2.includes("typeform.com") && parsed.pathname.includes("/to/")) {
|
|
17552
|
+
if (hostname2.includes("typeform.com") && parsed.pathname.includes("/to/"))
|
|
17421
17553
|
return "typeform";
|
|
17422
|
-
|
|
17423
|
-
if (hostname2.includes("tally.so")) {
|
|
17554
|
+
if (hostname2.includes("tally.so"))
|
|
17424
17555
|
return "tally";
|
|
17425
|
-
|
|
17556
|
+
if (videoask.detect(url))
|
|
17557
|
+
return "videoask";
|
|
17426
17558
|
return null;
|
|
17427
17559
|
}
|
|
17560
|
+
|
|
17561
|
+
// src/tools/import-form.ts
|
|
17562
|
+
function detectImportPlatform(url) {
|
|
17563
|
+
const provider = detectProvider(url);
|
|
17564
|
+
return provider && IMPORTABLE_PROVIDERS.includes(provider) ? provider : null;
|
|
17565
|
+
}
|
|
17428
17566
|
function pluralQuestions(n) {
|
|
17429
17567
|
return `${n} question${n === 1 ? "" : "s"}`;
|
|
17430
17568
|
}
|
|
@@ -17435,6 +17573,7 @@ function registerImportFormTool(server) {
|
|
|
17435
17573
|
title: "Import Form",
|
|
17436
17574
|
description: `Convert a public Google Form, Typeform, or Tally form into a new Clipform. Supported URLs: Google Forms (docs.google.com or forms.gle - must be shared as "Anyone with the link"), Typeform (form.typeform.com/to/...), and Tally (tally.so - must be public). The source form must be publicly accessible; a private form's questions cannot be read. The new Clipform is created as a DRAFT - review it, then republish with clipform_update_form (is_live: true) once it's ready. Some question types don't map cleanly to a Clipform node and are skipped rather than guessed at; the result always reports exactly what was imported vs skipped, and why.`,
|
|
17437
17575
|
inputSchema: {
|
|
17576
|
+
workspace_id: workspaceIdInput,
|
|
17438
17577
|
url: external_exports.string().url().describe("Public URL of the Google Form, Typeform, or Tally form to import")
|
|
17439
17578
|
},
|
|
17440
17579
|
outputSchema: {
|
|
@@ -17447,7 +17586,8 @@ function registerImportFormTool(server) {
|
|
|
17447
17586
|
skipped: external_exports.number().describe("Questions that could not be converted"),
|
|
17448
17587
|
skipped_details: external_exports.array(external_exports.record(external_exports.string(), external_exports.string())).describe("Per-skipped-question title, reason, and source question type"),
|
|
17449
17588
|
warnings: external_exports.array(external_exports.string()).describe("Non-fatal issues surfaced during the import")
|
|
17450
|
-
})
|
|
17589
|
+
}),
|
|
17590
|
+
workspace_name: external_exports.string().optional().describe("Name of the workspace the form was created in")
|
|
17451
17591
|
},
|
|
17452
17592
|
annotations: {
|
|
17453
17593
|
readOnlyHint: false,
|
|
@@ -17471,6 +17611,7 @@ function registerImportFormTool(server) {
|
|
|
17471
17611
|
}
|
|
17472
17612
|
const me = meResult.data;
|
|
17473
17613
|
const workspaceId = me.workspace?.id ?? null;
|
|
17614
|
+
const workspaceName = me.auth_mode && me.auth_mode !== "anonymous" ? me.workspace?.name ?? void 0 : void 0;
|
|
17474
17615
|
if (!workspaceId) {
|
|
17475
17616
|
return errorResult(
|
|
17476
17617
|
"No workspace available. This usually means authentication is misconfigured - check your API key or OAuth connection."
|
|
@@ -17513,7 +17654,9 @@ function registerImportFormTool(server) {
|
|
|
17513
17654
|
`Pass form_id (the UUID above, not a share_id) to follow-up tools (get_form, add_node, update_node, etc.).`
|
|
17514
17655
|
);
|
|
17515
17656
|
}
|
|
17657
|
+
if (formId && workspaceName) lines.push(``, `Created in workspace '${workspaceName}'.`);
|
|
17516
17658
|
return structuredResult(lines.join("\n"), {
|
|
17659
|
+
...workspaceName ? { workspace_name: workspaceName } : {},
|
|
17517
17660
|
form_id: formId,
|
|
17518
17661
|
viewer_url: viewerUrl,
|
|
17519
17662
|
import_summary: {
|
|
@@ -17537,6 +17680,7 @@ function registerListFormsTool(server) {
|
|
|
17537
17680
|
title: "List Clipforms",
|
|
17538
17681
|
description: `List forms in your workspace with optional filtering. Returns paginated results (cursor-based). next_cursor is included when more results exist. To find or open a form by name, pass search with part of its title instead of paging through results.`,
|
|
17539
17682
|
inputSchema: {
|
|
17683
|
+
workspace_id: workspaceIdInput,
|
|
17540
17684
|
limit: external_exports.number().int().min(1).max(100).optional().describe("Number of forms to return (default 25, max 100)"),
|
|
17541
17685
|
cursor: external_exports.string().optional().describe(
|
|
17542
17686
|
"Pagination cursor from previous response's next_cursor"
|
|
@@ -18356,7 +18500,8 @@ function registerWhoamiTool(server) {
|
|
|
18356
18500
|
node_limit: external_exports.number().nullable().describe("Max nodes per form, or null if unlimited"),
|
|
18357
18501
|
form_limit: external_exports.number().nullable().describe("Max forms in the workspace, or null if unlimited")
|
|
18358
18502
|
}).describe("Plan limits for the active workspace"),
|
|
18359
|
-
workspaces: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string() })).optional().describe("
|
|
18503
|
+
workspaces: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string() })).optional().describe("Workspaces the caller can target with workspace_id - present for session (dashboard) auth and the OAuth connector"),
|
|
18504
|
+
default_workspace_id: external_exports.string().optional().describe("OAuth connector only: the workspace used when a tool call omits workspace_id"),
|
|
18360
18505
|
links: external_exports.object({
|
|
18361
18506
|
dashboard: external_exports.string().describe("Dashboard home for the active workspace."),
|
|
18362
18507
|
forms: external_exports.string().describe("Forms list for the active workspace.")
|
|
@@ -18394,11 +18539,26 @@ function registerWhoamiTool(server) {
|
|
|
18394
18539
|
if (Array.isArray(me.workspaces)) {
|
|
18395
18540
|
structured.workspaces = me.workspaces.map((w) => ({ id: w.id, name: w.name }));
|
|
18396
18541
|
}
|
|
18542
|
+
const ctx = getMcpAuth();
|
|
18543
|
+
if (!structured.workspaces && ctx?.listWorkspaces) {
|
|
18544
|
+
try {
|
|
18545
|
+
const list = await ctx.listWorkspaces();
|
|
18546
|
+
structured.workspaces = list.map((w) => ({ id: w.id, name: w.name }));
|
|
18547
|
+
if (ctx.default_workspace_id) structured.default_workspace_id = ctx.default_workspace_id;
|
|
18548
|
+
} catch {
|
|
18549
|
+
}
|
|
18550
|
+
}
|
|
18397
18551
|
if (me.links && typeof me.links.dashboard === "string" && typeof me.links.forms === "string") {
|
|
18398
18552
|
structured.links = { dashboard: me.links.dashboard, forms: me.links.forms };
|
|
18399
18553
|
}
|
|
18400
18554
|
const workspaceLine = workspace ? `workspace '${workspace.name}'` : "no workspace (anonymous)";
|
|
18401
18555
|
const lines = [`Auth: ${authMode}. ${workspaceLine}. Plan: ${plan.name}.`];
|
|
18556
|
+
if (Array.isArray(structured.workspaces) && ctx?.listWorkspaces) {
|
|
18557
|
+
const list = structured.workspaces;
|
|
18558
|
+
lines.push(
|
|
18559
|
+
`Workspaces you can act in (pass one of these ids as the workspace argument to target it; omit it to use the default): ` + list.map((w) => `${w.name} (${w.id})`).join(", ")
|
|
18560
|
+
);
|
|
18561
|
+
}
|
|
18402
18562
|
if (structured.links) {
|
|
18403
18563
|
const links = structured.links;
|
|
18404
18564
|
lines.push(`Dashboard: ${links.dashboard}`, `Forms list: ${links.forms}`);
|
|
@@ -18587,7 +18747,8 @@ function registerDeleteNodeTool(server) {
|
|
|
18587
18747
|
|
|
18588
18748
|
// src/tools/upload-media-asset.ts
|
|
18589
18749
|
var MediaAssetItemSchema = external_exports.object({
|
|
18590
|
-
media_type: external_exports.enum(["video", "still"]).describe("Type of media"),
|
|
18750
|
+
media_type: external_exports.enum(["video", "still", "document"]).describe("Type of media. Use document for a consent-agreement PDF (url required, PDF only)."),
|
|
18751
|
+
filename: external_exports.string().max(200).optional().describe("Display filename for a document (media_type document). Defaults to the URL's file name."),
|
|
18591
18752
|
media_source: external_exports.enum(["uploaded", "recorded"]).default("uploaded").describe("How the media was created"),
|
|
18592
18753
|
url: external_exports.string().url().optional().describe("Public URL that links directly to the media file itself. When provided, the server fetches and stores it directly."),
|
|
18593
18754
|
captions: external_exports.array(
|
|
@@ -18633,9 +18794,16 @@ function mapFileEntryToItem(file) {
|
|
|
18633
18794
|
if (category === "video") {
|
|
18634
18795
|
return { ok: true, item: { media_type: "video", media_source: "uploaded", url: file.download_url } };
|
|
18635
18796
|
}
|
|
18797
|
+
if (mimeType === "application/pdf") {
|
|
18798
|
+
const filename = file.name ?? file.file_name;
|
|
18799
|
+
return {
|
|
18800
|
+
ok: true,
|
|
18801
|
+
item: { media_type: "document", media_source: "uploaded", url: file.download_url, ...filename ? { filename } : {} }
|
|
18802
|
+
};
|
|
18803
|
+
}
|
|
18636
18804
|
return {
|
|
18637
18805
|
ok: false,
|
|
18638
|
-
error: `Attached file type "${mimeType || "unknown"}" is not supported. Attach an image or
|
|
18806
|
+
error: `Attached file type "${mimeType || "unknown"}" is not supported. Attach an image, video or PDF file, or provide a direct URL instead.`
|
|
18639
18807
|
};
|
|
18640
18808
|
}
|
|
18641
18809
|
function registerUploadMediaAssetTool(server) {
|
|
@@ -18645,38 +18813,55 @@ function registerUploadMediaAssetTool(server) {
|
|
|
18645
18813
|
title: "Upload Media Asset",
|
|
18646
18814
|
description: `Put one or more media files into your workspace media library (max 10, uploaded sequentially). This is step one of attaching media to a node - follow up with clipform_attach_node_media to place the returned media_asset_id on a node.
|
|
18647
18815
|
|
|
18648
|
-
When a public URL is provided, the media is fetched and stored automatically.
|
|
18816
|
+
When a public URL is provided, the media is fetched and stored automatically. The API picks where each asset is stored based on its size and type. Word-level captions from clipform_generate_tts enable per-word highlighting in the viewer once attached - pass the caption_ref it returned instead of hand-copying the captions array.
|
|
18817
|
+
|
|
18818
|
+
PDFs (media_type document) are for consent agreements: they return a document descriptor, not a media_asset_id.
|
|
18649
18819
|
|
|
18650
18820
|
Provide media via \`items\` (explicit type + URL), \`files\` (files attached by the host client), or both. At least one is required.`,
|
|
18651
18821
|
inputSchema: {
|
|
18822
|
+
workspace_id: workspaceIdInput,
|
|
18823
|
+
form_id: formIdTargetInput,
|
|
18652
18824
|
items: external_exports.array(MediaAssetItemSchema).min(1).max(10).optional().describe("One or more media items to create as workspace library assets"),
|
|
18653
|
-
files: external_exports.array(FileEntrySchema).min(1).max(10).optional().describe("Files attached by the host client. Each becomes a video or still library asset based on its declared mime_type.")
|
|
18825
|
+
files: external_exports.array(FileEntrySchema).min(1).max(10).optional().describe("Files attached by the host client. Each becomes a video or still library asset (or a consent-agreement document for application/pdf) based on its declared mime_type.")
|
|
18654
18826
|
},
|
|
18655
18827
|
outputSchema: {
|
|
18656
18828
|
results: external_exports.array(
|
|
18657
18829
|
external_exports.object({
|
|
18658
|
-
media_asset_id: external_exports.string().optional(),
|
|
18830
|
+
media_asset_id: external_exports.string().optional().describe("Absent for documents"),
|
|
18659
18831
|
media_type: external_exports.string(),
|
|
18832
|
+
document: external_exports.object({
|
|
18833
|
+
storage_path: external_exports.string(),
|
|
18834
|
+
filename: external_exports.string(),
|
|
18835
|
+
url: external_exports.string(),
|
|
18836
|
+
version: external_exports.string()
|
|
18837
|
+
}).optional().describe("Consent-agreement descriptor (documents only) - set as consent_items[i].document via clipform_update_node"),
|
|
18660
18838
|
stored: external_exports.boolean().describe("Bytes already stored (URL-based); false when an upload_url is pending"),
|
|
18661
18839
|
upload_url: external_exports.string().optional(),
|
|
18662
18840
|
upload_method: external_exports.string().optional(),
|
|
18663
18841
|
ok: external_exports.boolean(),
|
|
18664
18842
|
error: external_exports.string().optional()
|
|
18665
18843
|
})
|
|
18666
|
-
).describe("One result per item, in order")
|
|
18844
|
+
).describe("One result per item, in order"),
|
|
18845
|
+
workspace_name: external_exports.string().optional().describe("Name of the workspace the media library entries were created in")
|
|
18667
18846
|
},
|
|
18668
18847
|
annotations: {
|
|
18669
18848
|
readOnlyHint: false,
|
|
18670
18849
|
destructiveHint: false,
|
|
18671
18850
|
idempotentHint: false,
|
|
18672
|
-
|
|
18851
|
+
// Open-world: fetches an arbitrary caller-supplied URL (and may hand
|
|
18852
|
+
// larger video to a third-party streaming provider) (#4434).
|
|
18853
|
+
openWorldHint: true
|
|
18673
18854
|
},
|
|
18674
18855
|
// Apps SDK convention (openai-apps-sdk-examples#185): a host client only
|
|
18675
18856
|
// hydrates a file attachment into a TOP-LEVEL tool argument named here -
|
|
18676
18857
|
// same mechanism create-form.ts uses for anthropic/alwaysLoad.
|
|
18677
18858
|
_meta: { "openai/fileParams": ["files"] }
|
|
18678
18859
|
},
|
|
18679
|
-
|
|
18860
|
+
// `form_id` is consumed centrally by `runWithWorkspaceTarget` (server.ts)
|
|
18861
|
+
// BEFORE this handler runs - it's a routing hint only (#4621), never
|
|
18862
|
+
// forwarded to the API: this route takes no form_id and /me below still
|
|
18863
|
+
// resolves the (now-targeted) workspace.
|
|
18864
|
+
async ({ items, files, form_id: _form_id }) => {
|
|
18680
18865
|
if ((!items || items.length === 0) && (!files || files.length === 0)) {
|
|
18681
18866
|
return errorResult("Provide at least one media item via `items` or `files`.");
|
|
18682
18867
|
}
|
|
@@ -18690,6 +18875,7 @@ Provide media via \`items\` (explicit type + URL), \`files\` (files attached by
|
|
|
18690
18875
|
}
|
|
18691
18876
|
const me = meResult.data;
|
|
18692
18877
|
const workspaceId = me.workspace?.id ?? null;
|
|
18878
|
+
const workspaceName = me.auth_mode && me.auth_mode !== "anonymous" ? me.workspace?.name ?? void 0 : void 0;
|
|
18693
18879
|
if (!workspaceId) {
|
|
18694
18880
|
return errorResult(
|
|
18695
18881
|
"No workspace available. This usually means authentication is misconfigured - check your API key or OAuth connection."
|
|
@@ -18721,6 +18907,7 @@ Provide media via \`items\` (explicit type + URL), \`files\` (files attached by
|
|
|
18721
18907
|
media_source: item.media_source
|
|
18722
18908
|
};
|
|
18723
18909
|
if (item.url) createBody.url = item.url;
|
|
18910
|
+
if (item.filename) createBody.filename = item.filename;
|
|
18724
18911
|
if (item.captions) createBody.captions = item.captions;
|
|
18725
18912
|
if (item.caption_ref) createBody.caption_ref = item.caption_ref;
|
|
18726
18913
|
if (item.ai_generated !== void 0) createBody.ai_generated = item.ai_generated;
|
|
@@ -18731,6 +18918,33 @@ Provide media via \`items\` (explicit type + URL), \`files\` (files attached by
|
|
|
18731
18918
|
results.push({ media_type: item.media_type, stored: false, ok: false, error: created.error });
|
|
18732
18919
|
continue;
|
|
18733
18920
|
}
|
|
18921
|
+
if (item.media_type === "document") {
|
|
18922
|
+
const d = created.data;
|
|
18923
|
+
if (typeof d.storage_path !== "string" || typeof d.url !== "string" || typeof d.version !== "string" || !d.version) {
|
|
18924
|
+
const error2 = "document upload did not return a document descriptor.";
|
|
18925
|
+
lines.push(`FAILED: ${error2}`);
|
|
18926
|
+
lines.push("");
|
|
18927
|
+
results.push({ media_type: "document", stored: false, ok: false, error: error2 });
|
|
18928
|
+
continue;
|
|
18929
|
+
}
|
|
18930
|
+
const document = {
|
|
18931
|
+
storage_path: d.storage_path,
|
|
18932
|
+
filename: String(d.filename ?? item.filename ?? "agreement.pdf"),
|
|
18933
|
+
url: d.url,
|
|
18934
|
+
version: d.version
|
|
18935
|
+
};
|
|
18936
|
+
successCount++;
|
|
18937
|
+
results.push({ media_type: "document", stored: true, ok: true, document });
|
|
18938
|
+
lines.push(
|
|
18939
|
+
[
|
|
18940
|
+
`Document stored: ${document.filename}`,
|
|
18941
|
+
`Descriptor: ${JSON.stringify(document)}`,
|
|
18942
|
+
`Set this on a Details node consent item via clipform_update_node: consent_items[i].document = <descriptor>, require_scroll_to_accept: true. Do not use clipform_attach_node_media.`
|
|
18943
|
+
].join("\n")
|
|
18944
|
+
);
|
|
18945
|
+
lines.push("");
|
|
18946
|
+
continue;
|
|
18947
|
+
}
|
|
18734
18948
|
const mediaAssetId = created.data.media_asset_id;
|
|
18735
18949
|
if (!mediaAssetId) {
|
|
18736
18950
|
const error2 = `upload did not return a media asset id (route ${String(created.data.route ?? "unknown")}).`;
|
|
@@ -18772,9 +18986,12 @@ Provide media via \`items\` (explicit type + URL), \`files\` (files attached by
|
|
|
18772
18986
|
lines.unshift(`Media asset upload: ${successCount}/${entries.length} succeeded
|
|
18773
18987
|
`);
|
|
18774
18988
|
}
|
|
18775
|
-
|
|
18989
|
+
if (results.some((r) => r.ok && r.media_asset_id)) {
|
|
18990
|
+
lines.push(`Attach these to nodes with clipform_attach_node_media.`);
|
|
18991
|
+
}
|
|
18776
18992
|
if (successCount === 0) return errorResult(lines.join("\n"));
|
|
18777
|
-
|
|
18993
|
+
if (workspaceName) lines.push(`Workspace: ${workspaceName}`);
|
|
18994
|
+
return structuredResult(lines.join("\n"), { results, ...workspaceName ? { workspace_name: workspaceName } : {} });
|
|
18778
18995
|
}
|
|
18779
18996
|
);
|
|
18780
18997
|
}
|
|
@@ -18785,9 +19002,10 @@ function registerCompleteMediaUploadTool(server) {
|
|
|
18785
19002
|
"clipform_complete_media_upload",
|
|
18786
19003
|
{
|
|
18787
19004
|
title: "Complete Media Upload",
|
|
18788
|
-
description: `Confirm a signed-PUT still image upload finished, after PUTting the bytes to the upload_url returned by clipform_upload_media_asset. The API verifies the object actually landed in storage before flipping the asset from processing to ready - call this right after the PUT succeeds, or the asset stays invisible in the library. Not needed for video uploads (
|
|
19005
|
+
description: `Confirm a signed-PUT still image upload finished, after PUTting the bytes to the upload_url returned by clipform_upload_media_asset. The API verifies the object actually landed in storage before flipping the asset from processing to ready - call this right after the PUT succeeds, or the asset stays invisible in the library. Not needed for video uploads (they settle automatically).`,
|
|
18789
19006
|
inputSchema: {
|
|
18790
|
-
media_asset_id: external_exports.string().uuid().describe("The media_asset_id returned by clipform_upload_media_asset for the still image")
|
|
19007
|
+
media_asset_id: external_exports.string().uuid().describe("The media_asset_id returned by clipform_upload_media_asset for the still image"),
|
|
19008
|
+
form_id: formIdTargetInput
|
|
18791
19009
|
},
|
|
18792
19010
|
outputSchema: {
|
|
18793
19011
|
media_asset_id: external_exports.string(),
|
|
@@ -18800,7 +19018,10 @@ function registerCompleteMediaUploadTool(server) {
|
|
|
18800
19018
|
openWorldHint: false
|
|
18801
19019
|
}
|
|
18802
19020
|
},
|
|
18803
|
-
|
|
19021
|
+
// `form_id` (#4621) is a routing hint only, consumed centrally by
|
|
19022
|
+
// `runWithWorkspaceTarget` (server.ts) before this handler runs - never
|
|
19023
|
+
// forwarded to the API below (this route takes no form_id).
|
|
19024
|
+
async ({ media_asset_id, form_id: _form_id }) => {
|
|
18804
19025
|
const meResult = await callApi("/me", { method: "GET" });
|
|
18805
19026
|
if (!meResult.ok) {
|
|
18806
19027
|
return errorResult(`Unable to determine workspace: ${meResult.error}`);
|
|
@@ -18832,7 +19053,7 @@ var MEDIA_SUPPORTED_TYPES = Object.entries(NODE_TYPES).filter(([, def]) => def.s
|
|
|
18832
19053
|
var AttachMediaItemSchema = external_exports.object({
|
|
18833
19054
|
node_id: external_exports.string().describe("The node ID"),
|
|
18834
19055
|
media_asset_id: external_exports.string().describe("A media asset ID returned by clipform_upload_media_asset"),
|
|
18835
|
-
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
|
|
19056
|
+
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_render_video_template - they are composed 9:16 frames that must never be cropped. Leave unset for user-supplied media (arbitrary aspect ratios want the cover default)."),
|
|
18836
19057
|
show_captions: external_exports.boolean().optional().describe("Display captions/subtitles on the node. Defaults to on; pass false to hide them.")
|
|
18837
19058
|
});
|
|
18838
19059
|
function registerAttachNodeMediaTool(server) {
|
|
@@ -18979,7 +19200,7 @@ function registerDeleteNodeMediaTool(server) {
|
|
|
18979
19200
|
"clipform_delete_node_media",
|
|
18980
19201
|
{
|
|
18981
19202
|
title: "Delete Node Media",
|
|
18982
|
-
description: `Remove media from a node. Deletes the media record and
|
|
19203
|
+
description: `Remove media from a node. Deletes the media record and the stored media asset (including any copy held by a third-party video provider). Requires user confirmation before it runs.`,
|
|
18983
19204
|
inputSchema: {
|
|
18984
19205
|
form_id: external_exports.string().uuid().describe("The form UUID (returned by clipform_create_form, not the short share_id from the URL)"),
|
|
18985
19206
|
node_id: external_exports.string().describe("The node ID"),
|
|
@@ -18991,7 +19212,9 @@ function registerDeleteNodeMediaTool(server) {
|
|
|
18991
19212
|
// Deleting already-removed media has no further effect - textbook
|
|
18992
19213
|
// idempotent delete (#2976).
|
|
18993
19214
|
idempotentHint: true,
|
|
18994
|
-
|
|
19215
|
+
// Open-world: also deletes any copy held by a third-party video
|
|
19216
|
+
// provider (#4434).
|
|
19217
|
+
openWorldHint: true
|
|
18995
19218
|
}
|
|
18996
19219
|
},
|
|
18997
19220
|
async ({ form_id, node_id, confirm }) => {
|
|
@@ -19133,10 +19356,10 @@ function registerSearchNewsTool(server) {
|
|
|
19133
19356
|
server.registerTool(
|
|
19134
19357
|
"clipform_search_news",
|
|
19135
19358
|
{
|
|
19136
|
-
title: "Search News
|
|
19137
|
-
description: `
|
|
19359
|
+
title: "Search Recent News",
|
|
19360
|
+
description: `Search recent news articles from NewsAPI and The Guardian and return them as structured results.
|
|
19138
19361
|
|
|
19139
|
-
Coverage: recent events, people, and topics (post-May-2025). Does NOT cover timeless topics (history, geography, science).
|
|
19362
|
+
Coverage: recent events, people, and topics (post-May-2025). Does NOT cover timeless topics (history, geography, science).
|
|
19140
19363
|
|
|
19141
19364
|
Returns: article title, source, author, date, URL, description, and image URL per result.`,
|
|
19142
19365
|
inputSchema: {
|
|
@@ -19221,6 +19444,8 @@ Use the tone parameter to direct HOW the voice speaks. Always set a tone that ma
|
|
|
19221
19444
|
|
|
19222
19445
|
Pass one item or many (max 10) - multiple items run in parallel. Returns audio_url and caption_ref per item - pass caption_ref downstream to clipform_render_composition / clipform_render_video_template / clipform_upload_media_asset to attach this run's word-level captions instead of hand-copying the captions array.`,
|
|
19223
19446
|
inputSchema: {
|
|
19447
|
+
workspace_id: workspaceIdInput,
|
|
19448
|
+
form_id: formIdTargetInput,
|
|
19224
19449
|
items: external_exports.array(TtsItemSchema).min(1).max(10).describe("One or more TTS items to generate")
|
|
19225
19450
|
},
|
|
19226
19451
|
outputSchema: {
|
|
@@ -19236,7 +19461,11 @@ Pass one item or many (max 10) - multiple items run in parallel. Returns audio_u
|
|
|
19236
19461
|
},
|
|
19237
19462
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
|
|
19238
19463
|
},
|
|
19239
|
-
|
|
19464
|
+
// `form_id` (#4621) is a routing hint only, consumed centrally by
|
|
19465
|
+
// `runWithWorkspaceTarget` (server.ts) before this handler runs - it
|
|
19466
|
+
// swaps the auth context's workspace_id read below, but is never
|
|
19467
|
+
// forwarded to the API itself (the /internal/tts route takes no form_id).
|
|
19468
|
+
async ({ items, form_id: _form_id }) => {
|
|
19240
19469
|
const workspace_id = getMcpAuth()?.workspace_id;
|
|
19241
19470
|
const results = await Promise.allSettled(
|
|
19242
19471
|
items.map(
|
|
@@ -19301,7 +19530,7 @@ function registerSearchMediaTool(server) {
|
|
|
19301
19530
|
"clipform_search_media",
|
|
19302
19531
|
{
|
|
19303
19532
|
title: "Search Media",
|
|
19304
|
-
description: `Search images or stock video clips. Pass one query or many (max 10) - multiple queries run in one call instead of separate tool calls. Use results to feed into
|
|
19533
|
+
description: `Search images or stock video clips. Pass one query or many (max 10) - multiple queries run in one call instead of separate tool calls. Use results to feed into clipform_render_video_template (slideshow) for narrated slideshow videos, or upload directly as still images via clipform_upload_media_asset then clipform_attach_node_media. All results are pre-cleared for commercial use. Results include a description (alt text where the provider has it) - use it to pick visually distinct images.
|
|
19305
19534
|
|
|
19306
19535
|
Example: { queries: [{ query: "saturn rings" }, { query: "mars surface", count: 3 }] } returns portrait images for both.`,
|
|
19307
19536
|
inputSchema: {
|
|
@@ -19395,8 +19624,7 @@ Example: { queries: [{ query: "saturn rings" }, { query: "mars surface", count:
|
|
|
19395
19624
|
}
|
|
19396
19625
|
|
|
19397
19626
|
// src/lib/render-routing.ts
|
|
19398
|
-
var RENDER_ROUTING_RULE = `Choosing a render tool: for a recognisable form/quiz beat (guess-the-city, this-or-that, mystery reveal, multiple choice, photo montage...) reach for a video template first (clipform_list_video_templates + clipform_render_video_template) - it is a one-call recipe.
|
|
19399
|
-
var MONTAGE_NARRATION_RULE = `Montage disambiguation: choose clipform_generate_video when the montage is narrated or synced to an audio track; choose the slideshow video template when it is silent (motion + transitions only, no voice-over).`;
|
|
19627
|
+
var RENDER_ROUTING_RULE = `Choosing a render tool: for a recognisable form/quiz beat (guess-the-city, this-or-that, mystery reveal, multiple choice, photo montage...) reach for a video template first (clipform_list_video_templates + clipform_render_video_template) - it is a one-call recipe. For a photo/clip montage, narrated or silent, use the slideshow template (pass audio_url to sync it to a voice track). Use clipform_render_composition only when neither fits and you need a custom layer stack.`;
|
|
19400
19628
|
var RENDER_ATTACH_RULE = `A render for a form node is not done until it is attached to that node. Pass node_id (and form_id) so the completed render attaches itself automatically - do not poll clipform_check_render to completion or manually chain clipform_upload_media_asset + clipform_attach_node_media; fire the render and move on.`;
|
|
19401
19629
|
|
|
19402
19630
|
// ../../node_modules/.pnpm/uuid@11.1.1/node_modules/uuid/dist/esm/stringify.js
|
|
@@ -19680,7 +19908,7 @@ function registerRenderCompositionTool(server) {
|
|
|
19680
19908
|
"clipform_render_composition",
|
|
19681
19909
|
{
|
|
19682
19910
|
title: "Render Composition",
|
|
19683
|
-
description: `Render a specialised video composition to MP4 or PNG - custom animated visuals that
|
|
19911
|
+
description: `Render a specialised video composition to MP4 or PNG - custom animated visuals that a video template can't provide, such as geography animations or designed motion graphics. Available composition names and their required input props schemas come from clipform_list_compositions.
|
|
19684
19912
|
|
|
19685
19913
|
Output formats: mp4 (H.264, best for social media) or png (single frame). Returns a public URL when complete.
|
|
19686
19914
|
|
|
@@ -19825,7 +20053,7 @@ function registerSearchMusicTool(server) {
|
|
|
19825
20053
|
"clipform_search_music",
|
|
19826
20054
|
{
|
|
19827
20055
|
title: "Search Music",
|
|
19828
|
-
description: `Search for royalty-free music tracks and ambient sounds. Quiz forms especially benefit from background music - it keeps energy up between questions. Also consider it for multi-step surveys and longer forms. Pass the returned URL as the audio
|
|
20056
|
+
description: `Search for royalty-free music tracks and ambient sounds. Quiz forms especially benefit from background music - it keeps energy up between questions. Also consider it for multi-step surveys and longer forms. Pass the returned URL as the audio for the slideshow video template via clipform_render_video_template. Returns download URLs, duration, artist, and license info.`,
|
|
19829
20057
|
inputSchema: {
|
|
19830
20058
|
query: external_exports.string().min(1).describe("What to search for (e.g. 'upbeat quiz background', 'calm ambient', 'playful pizzicato')"),
|
|
19831
20059
|
count: external_exports.number().int().min(1).max(10).default(5).optional().describe("Max results (default: 5)"),
|
|
@@ -19845,7 +20073,7 @@ function registerSearchMusicTool(server) {
|
|
|
19845
20073
|
source: external_exports.string().optional(),
|
|
19846
20074
|
license: external_exports.string().optional()
|
|
19847
20075
|
})
|
|
19848
|
-
).describe("Matching tracks (empty if none); pass a url as the audio
|
|
20076
|
+
).describe("Matching tracks (empty if none); pass a url as the audio for the slideshow template")
|
|
19849
20077
|
},
|
|
19850
20078
|
// Fetches from an external royalty-free music provider - openWorldHint:
|
|
19851
20079
|
// true (#2976).
|
|
@@ -19915,7 +20143,7 @@ function registerListCompositionsTool(server) {
|
|
|
19915
20143
|
"clipform_list_compositions",
|
|
19916
20144
|
{
|
|
19917
20145
|
title: "List Compositions",
|
|
19918
|
-
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 an image or photo montage, reach for a video template (clipform_list_video_templates + clipform_render_video_template)
|
|
20146
|
+
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 an image or photo montage, reach for a video template (clipform_list_video_templates + clipform_render_video_template) instead of a composition.`,
|
|
19919
20147
|
inputSchema: {},
|
|
19920
20148
|
outputSchema: {
|
|
19921
20149
|
compositions: external_exports.array(
|
|
@@ -20214,180 +20442,6 @@ function registerListAssetsTool(server) {
|
|
|
20214
20442
|
);
|
|
20215
20443
|
}
|
|
20216
20444
|
|
|
20217
|
-
// src/tools/generate-video.ts
|
|
20218
|
-
var httpUrl = () => external_exports.string().url().refine(isHttpUrl, { message: HTTP_URL_MESSAGE });
|
|
20219
|
-
function registerGenerateVideoTool(server) {
|
|
20220
|
-
server.registerTool(
|
|
20221
|
-
"clipform_generate_video",
|
|
20222
|
-
{
|
|
20223
|
-
title: "Generate Video",
|
|
20224
|
-
description: `Generate a video from images, video clips, or both, synced to an audio track. Use this for narrated question backgrounds, topic visualisations, or any form node that benefits from video. Combine with clipform_generate_tts for narrated audio and clipform_search_media for royalty-free images. Creates 9:16 (720x1280) with Ken Burns pan/zoom effects and transitions. Returns a public URL when complete.
|
|
20225
|
-
|
|
20226
|
-
Items: type "image" (Ken Burns motion) or "video" (cover-cropped, muted by default). Duration matches audio_url or set duration_seconds explicitly.
|
|
20227
|
-
|
|
20228
|
-
For multi-question builds, pass wait: false on every render: each call returns a job ID immediately, so all renders run in parallel - then collect URLs with clipform_check_render. Sequential waiting renders take ${RENDER_TIMING.expectedRange} EACH.
|
|
20229
|
-
|
|
20230
|
-
${RENDER_ROUTING_RULE}
|
|
20231
|
-
${MONTAGE_NARRATION_RULE}
|
|
20232
|
-
${RENDER_ATTACH_RULE}`,
|
|
20233
|
-
inputSchema: {
|
|
20234
|
-
items: external_exports.array(
|
|
20235
|
-
external_exports.object({
|
|
20236
|
-
type: external_exports.enum(["image", "video"]).describe("'image' for still images with Ken Burns effects, 'video' for video clips"),
|
|
20237
|
-
url: httpUrl().describe("Media URL"),
|
|
20238
|
-
effect: external_exports.enum([...KEN_BURNS_EFFECTS, "random"]).optional().describe("Advanced override: a specific Ken Burns effect (image only), overriding the preset look's own effect choice"),
|
|
20239
|
-
aspect_ratio: external_exports.number().positive().optional().describe("Image width/height ratio (image only). Enables auto blur-pad for landscape images."),
|
|
20240
|
-
fit: external_exports.enum(KEN_BURNS_FIT_MODES).optional().describe("Framing mode (image only). auto = cover for portrait, blur-pad for landscape."),
|
|
20241
|
-
start_from: external_exports.number().min(0).optional().describe("Start time in seconds (video only). Trims the clip."),
|
|
20242
|
-
volume: external_exports.number().min(0).max(1).optional().describe("Clip audio volume 0-1 (video only, default 0 = muted)")
|
|
20243
|
-
})
|
|
20244
|
-
).min(1).max(20).describe("Media items (images, video clips, or a mix)"),
|
|
20245
|
-
audio_url: httpUrl().optional().describe("Audio track URL. Video duration matches audio duration."),
|
|
20246
|
-
background_audio_url: httpUrl().optional().describe("Ambience/music bed under the narration (crowd noise, room tone). Loops to fill the video. Find tracks with clipform_search_music."),
|
|
20247
|
-
background_audio_volume: external_exports.number().min(0).max(1).optional().describe("Background bed volume 0-1 (default 0.15 - sits under speech)"),
|
|
20248
|
-
duration_seconds: external_exports.number().positive().optional().describe("Video duration in seconds (required if no audio_url)"),
|
|
20249
|
-
preset: external_exports.enum(VIDEO_LOOK_NAMES).optional().default("clean").describe(`Named look driving transition + motion + colour treatment together (default: 'clean'). One of: ${VIDEO_LOOK_NAMES.join(", ")}. The advanced fields below (transition, style_preset, texture, duotone, background_color, per-item effect) override individual pieces of the chosen look when set.`),
|
|
20250
|
-
random_effects: external_exports.boolean().optional().default(true).describe("Shuffle Ken Burns effects across image items (default: true)"),
|
|
20251
|
-
transition: external_exports.object({
|
|
20252
|
-
type: external_exports.enum(SLIDESHOW_COMPOSITION_TRANSITIONS).optional().describe(`Advanced override: ${SLIDESHOW_COMPOSITION_TRANSITIONS.join(", ")} - overrides the preset look's own transition`),
|
|
20253
|
-
duration: external_exports.number().min(SLIDESHOW_TRANSITION_DURATION.min).max(SLIDESHOW_TRANSITION_DURATION.max).optional().describe(`Advanced override: transition duration in seconds (${SLIDESHOW_TRANSITION_DURATION.min}-${SLIDESHOW_TRANSITION_DURATION.max}). For a hard cut use type: 'none' instead - duration is ignored then.`)
|
|
20254
|
-
}).optional().describe("Advanced: overrides the preset look's transition"),
|
|
20255
|
-
style_preset: STYLE_PRESET_ENUM.optional().describe(`Advanced override: a raw Ken Burns motion preset, overriding the preset look's own motion.`),
|
|
20256
|
-
texture: external_exports.object({
|
|
20257
|
-
type: external_exports.enum(["checker", "halftone", "scanlines"]).describe("checker = square dither mesh, halftone = dot grid, scanlines = horizontal lines"),
|
|
20258
|
-
sizePx: external_exports.number().positive().optional().describe("Pattern cell size in px (default 4)"),
|
|
20259
|
-
opacity: external_exports.number().min(0).max(1).optional().describe("Pattern darkness 0-1 (default 0.35)")
|
|
20260
|
-
}).optional().describe("Advanced override: print-style pattern overlay on image items, overriding the preset look's own texture - makes stock imagery read as designed (screen-print dither look)"),
|
|
20261
|
-
duotone: external_exports.object({
|
|
20262
|
-
shadow: external_exports.string().describe("Darker tone (maps to image shadows), e.g. '#1a1633'"),
|
|
20263
|
-
highlight: external_exports.string().describe("Lighter tone (maps to image highlights), e.g. '#f5c542'"),
|
|
20264
|
-
contrast: external_exports.number().positive().optional().describe("Contrast boost before mapping (default 1.1)")
|
|
20265
|
-
}).optional().describe("Advanced override: two-tone editorial recolour on image items, overriding the preset look's own duotone - desaturates then maps to a shadow->highlight palette. Pair with a halftone texture for a screen-print poster look."),
|
|
20266
|
-
background_color: external_exports.string().optional().describe("Advanced override: background color, overriding the preset look's own background (default '#000')"),
|
|
20267
|
-
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."),
|
|
20268
|
-
node_id: external_exports.string().optional().describe("Form node to attach this render to automatically once it completes - skips the manual clipform_upload_media_asset + clipform_attach_node_media steps and republishes the form if it's currently live. Requires form_id."),
|
|
20269
|
-
form_id: external_exports.string().uuid().optional().describe("The form UUID (required when node_id is set)."),
|
|
20270
|
-
captions: external_exports.array(CaptionSchema).optional().describe("Word-level captions from clipform_generate_tts - carried onto the attached media asset. Only used when node_id is set.")
|
|
20271
|
-
},
|
|
20272
|
-
outputSchema: {
|
|
20273
|
-
status: external_exports.enum(["rendering", "complete"]).describe("'rendering' when wait:false (poll check_render); 'complete' with a public_url when wait:true"),
|
|
20274
|
-
job_id: external_exports.string().optional().describe("Present when status is 'rendering' - pass to check_render"),
|
|
20275
|
-
public_url: external_exports.string().optional().describe("Present when status is 'complete' - attach via upload_media_asset then attach_node_media (fit_media: true), unless node_id was set (auto-attached)"),
|
|
20276
|
-
duration_seconds: external_exports.number().optional(),
|
|
20277
|
-
attached: external_exports.boolean().optional().describe("True when node_id was provided and the render was attached to the node automatically (present once the attach outcome is known)."),
|
|
20278
|
-
media_asset_id: external_exports.string().optional().describe("The workspace media asset created from this render, when attached."),
|
|
20279
|
-
republished: external_exports.boolean().optional().describe("True when the form was live and was republished to include this media."),
|
|
20280
|
-
attach_error: external_exports.string().optional().describe("Present when node_id was provided but auto-attach failed - the render itself still succeeded.")
|
|
20281
|
-
},
|
|
20282
|
-
// node_id auto-attach replaces the node's existing media placement and
|
|
20283
|
-
// republishes an already-live form - an indirect but real overwrite +
|
|
20284
|
-
// publish (#2976 research pass 2), even though this tool works with no
|
|
20285
|
-
// node_id too (destructive/openWorld apply "even in only select modes").
|
|
20286
|
-
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true }
|
|
20287
|
-
},
|
|
20288
|
-
async ({ items, audio_url, background_audio_url, background_audio_volume, duration_seconds, preset, random_effects, transition, style_preset, texture, duotone, background_color, wait, node_id, form_id, captions }) => {
|
|
20289
|
-
if (node_id && !form_id) {
|
|
20290
|
-
return errorResult("form_id is required when node_id is set.");
|
|
20291
|
-
}
|
|
20292
|
-
const default_style = style_preset || texture || duotone ? {
|
|
20293
|
-
...style_preset ? { preset: style_preset } : {},
|
|
20294
|
-
...texture ? { texture } : {},
|
|
20295
|
-
...duotone ? { duotone } : {}
|
|
20296
|
-
} : void 0;
|
|
20297
|
-
const apiCall = () => callApi("/internal/generate-video", {
|
|
20298
|
-
timeoutMs: 3e5,
|
|
20299
|
-
// local renders pay a cold webpack bundle; Lambda is fast but bursty
|
|
20300
|
-
body: {
|
|
20301
|
-
items,
|
|
20302
|
-
audio_url,
|
|
20303
|
-
background_audio_url,
|
|
20304
|
-
background_audio_volume,
|
|
20305
|
-
duration_seconds,
|
|
20306
|
-
// preset expands to its base transition/style/background on the
|
|
20307
|
-
// API side (expandVideoLook); transition/default_style/background_color
|
|
20308
|
-
// below are only sent when the caller actually set the advanced
|
|
20309
|
-
// override, so the API can tell "explicit" apart from "unset" and
|
|
20310
|
-
// apply the look's own base value instead (#4038).
|
|
20311
|
-
preset,
|
|
20312
|
-
random_effects: random_effects ?? true,
|
|
20313
|
-
transition,
|
|
20314
|
-
default_style,
|
|
20315
|
-
background_color
|
|
20316
|
-
}
|
|
20317
|
-
});
|
|
20318
|
-
if (wait === false) {
|
|
20319
|
-
const job = createJob("clipform_generate_video");
|
|
20320
|
-
const placeholderPromise2 = node_id && form_id ? createRenderPlaceholder({ formId: form_id, nodeId: node_id, mediaType: "video" }) : Promise.resolve(void 0);
|
|
20321
|
-
apiCall().then(async (r) => {
|
|
20322
|
-
if (!r.ok) {
|
|
20323
|
-
failJob(job.id, r.error);
|
|
20324
|
-
const placeholder = await placeholderPromise2;
|
|
20325
|
-
if (placeholder) await failPlaceholder(placeholder, r.error);
|
|
20326
|
-
return;
|
|
20327
|
-
}
|
|
20328
|
-
let attach2;
|
|
20329
|
-
if (node_id && form_id) {
|
|
20330
|
-
const publicUrl = r.data.public_url;
|
|
20331
|
-
const placeholder = await placeholderPromise2;
|
|
20332
|
-
if (publicUrl) {
|
|
20333
|
-
attach2 = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl, mediaType: "video", captions, captionRef: resolveCaptionRef(void 0, audio_url), placeholder });
|
|
20334
|
-
} else {
|
|
20335
|
-
const message = "Render completed without a public_url; nothing to attach.";
|
|
20336
|
-
attach2 = { attached: false, error: message };
|
|
20337
|
-
if (placeholder) await failPlaceholder(placeholder, message);
|
|
20338
|
-
}
|
|
20339
|
-
}
|
|
20340
|
-
completeJob(job.id, r.data, attach2);
|
|
20341
|
-
}).catch(async (err) => {
|
|
20342
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
20343
|
-
failJob(job.id, message);
|
|
20344
|
-
const placeholder = await placeholderPromise2;
|
|
20345
|
-
if (placeholder) await failPlaceholder(placeholder, message);
|
|
20346
|
-
});
|
|
20347
|
-
return structuredResult(
|
|
20348
|
-
[
|
|
20349
|
-
`Render started (${items.length} item${items.length > 1 ? "s" : ""}).`,
|
|
20350
|
-
`Job ID: ${job.id}`,
|
|
20351
|
-
node_id ? `Will attach to node ${node_id} automatically once it completes - no need to poll to completion.` : `Fire any remaining renders now, then poll clipform_check_render. Renders typically take ${RENDER_TIMING.expectedRange}.`
|
|
20352
|
-
].join("\n"),
|
|
20353
|
-
{ status: "rendering", job_id: job.id }
|
|
20354
|
-
);
|
|
20355
|
-
}
|
|
20356
|
-
const placeholderPromise = node_id && form_id ? createRenderPlaceholder({ formId: form_id, nodeId: node_id, mediaType: "video" }) : Promise.resolve(void 0);
|
|
20357
|
-
const result = await apiCall();
|
|
20358
|
-
if (!result.ok) {
|
|
20359
|
-
const placeholder = await placeholderPromise;
|
|
20360
|
-
if (placeholder) await failPlaceholder(placeholder, result.error);
|
|
20361
|
-
return errorResult(result.error);
|
|
20362
|
-
}
|
|
20363
|
-
const data = result.data;
|
|
20364
|
-
let attach;
|
|
20365
|
-
if (node_id && form_id) {
|
|
20366
|
-
const placeholder = await placeholderPromise;
|
|
20367
|
-
if (data.public_url) {
|
|
20368
|
-
attach = await autoAttachRender({ formId: form_id, nodeId: node_id, publicUrl: data.public_url, mediaType: "video", captions, captionRef: resolveCaptionRef(void 0, audio_url), placeholder });
|
|
20369
|
-
} else if (placeholder) {
|
|
20370
|
-
await failPlaceholder(placeholder, "Render completed without a public_url; nothing to attach.");
|
|
20371
|
-
}
|
|
20372
|
-
}
|
|
20373
|
-
return structuredResult(
|
|
20374
|
-
[
|
|
20375
|
-
`Video rendered (${items.length} item${items.length > 1 ? "s" : ""}).`,
|
|
20376
|
-
`Public URL: ${data.public_url}`,
|
|
20377
|
-
data.duration_seconds ? `Duration: ${data.duration_seconds}s` : "",
|
|
20378
|
-
attach ? attachSummaryLine(node_id, attach) : `Attach via clipform_upload_media_asset then clipform_attach_node_media with fit_media: true (composed 9:16 frame - contain, never crop).`
|
|
20379
|
-
].filter(Boolean).join("\n"),
|
|
20380
|
-
{
|
|
20381
|
-
status: "complete",
|
|
20382
|
-
...data.public_url ? { public_url: data.public_url } : {},
|
|
20383
|
-
...typeof data.duration_seconds === "number" ? { duration_seconds: data.duration_seconds } : {},
|
|
20384
|
-
...attachStructuredFields(attach)
|
|
20385
|
-
}
|
|
20386
|
-
);
|
|
20387
|
-
}
|
|
20388
|
-
);
|
|
20389
|
-
}
|
|
20390
|
-
|
|
20391
20445
|
// src/tools/check-render.ts
|
|
20392
20446
|
function projectJob(id, job) {
|
|
20393
20447
|
if (!job) return { job_id: id, status: "not_found" };
|
|
@@ -20410,7 +20464,7 @@ var LOST_JOB_RECOVERY = [
|
|
|
20410
20464
|
`Jobs expire after 30 minutes and are lost if the server restarts - the render itself may still have completed.`,
|
|
20411
20465
|
`To recover, re-run the original render call with identical arguments:`,
|
|
20412
20466
|
`- clipform_render_composition results are content-cached: a finished render returns its URL immediately without re-rendering.`,
|
|
20413
|
-
`-
|
|
20467
|
+
`- clipform_render_video_template results are content-cached too: re-running with identical controls returns the finished URL.`
|
|
20414
20468
|
].join("\n");
|
|
20415
20469
|
function describeJob(id, job) {
|
|
20416
20470
|
if (!job) {
|
|
@@ -20436,7 +20490,7 @@ function registerCheckRenderTool(server) {
|
|
|
20436
20490
|
"clipform_check_render",
|
|
20437
20491
|
{
|
|
20438
20492
|
title: "Check Render Status",
|
|
20439
|
-
description: `Check the status of render jobs started by
|
|
20493
|
+
description: `Check the status of render jobs started by clipform_render_video_template or clipform_render_composition.
|
|
20440
20494
|
|
|
20441
20495
|
Pass job_ids to check a whole batch in ONE call - one line of status per job. Pass job_id for a single job. Returns the output URL for each completed render. Typical render time: 10-60 seconds. Attach is automatic when node_id was provided to the render tool - no need to poll to completion, and the attach outcome is reported here once known.`,
|
|
20442
20496
|
inputSchema: {
|
|
@@ -20816,7 +20870,7 @@ function createServer(options) {
|
|
|
20816
20870
|
config2,
|
|
20817
20871
|
(...args) => runWithMcpTool(name, async () => {
|
|
20818
20872
|
try {
|
|
20819
|
-
return await handler(...args);
|
|
20873
|
+
return await runWithWorkspaceTarget(args[0], () => handler(...args));
|
|
20820
20874
|
} catch (err) {
|
|
20821
20875
|
const error2 = err instanceof Error ? err : new Error(String(err));
|
|
20822
20876
|
options?.onToolError?.(name, error2);
|
|
@@ -20846,7 +20900,6 @@ function createServer(options) {
|
|
|
20846
20900
|
registerLogGenerationTool(server);
|
|
20847
20901
|
registerSearchNewsTool(server);
|
|
20848
20902
|
registerGenerateTtsTool(server);
|
|
20849
|
-
registerGenerateVideoTool(server);
|
|
20850
20903
|
registerSearchMediaTool(server);
|
|
20851
20904
|
registerRenderCompositionTool(server);
|
|
20852
20905
|
registerSearchMusicTool(server);
|
|
@@ -20870,4 +20923,4 @@ export {
|
|
|
20870
20923
|
buildInstructions,
|
|
20871
20924
|
createServer
|
|
20872
20925
|
};
|
|
20873
|
-
//# sourceMappingURL=chunk-
|
|
20926
|
+
//# sourceMappingURL=chunk-J7727374.js.map
|