@writepanda/mcp 1.92.0 → 1.99.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/bin/server.mjs +136 -15
- package/package.json +1 -1
package/bin/server.mjs
CHANGED
|
@@ -155,6 +155,26 @@ const TOOLS = [
|
|
|
155
155
|
inputSchema: { type: "object", properties: {} },
|
|
156
156
|
command: "system.status",
|
|
157
157
|
},
|
|
158
|
+
{
|
|
159
|
+
name: "agent_session_list",
|
|
160
|
+
description:
|
|
161
|
+
"List the embedded in-app agent's opencode sessions (id, title, created/updated timestamps). Observational only — never starts the agent server; returns running:false when it isn't up. Use before agent_session_stop to find the session to kill.",
|
|
162
|
+
inputSchema: { type: "object", properties: {} },
|
|
163
|
+
command: "agent.session-list",
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
name: "agent_session_stop",
|
|
167
|
+
description:
|
|
168
|
+
"Abort an in-app agent session by id (from agent_session_list), or every session with all:true. Stops that session's tool execution immediately; the transcript survives for replay. The kill switch for a runaway/ghost in-app agent session.",
|
|
169
|
+
inputSchema: {
|
|
170
|
+
type: "object",
|
|
171
|
+
properties: {
|
|
172
|
+
sessionId: { type: "string", description: "Session id to abort." },
|
|
173
|
+
all: { type: "boolean", description: "Abort every session instead of one." },
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
command: "agent.session-stop",
|
|
177
|
+
},
|
|
158
178
|
{
|
|
159
179
|
name: "system_list_commands",
|
|
160
180
|
description:
|
|
@@ -834,7 +854,7 @@ const TOOLS = [
|
|
|
834
854
|
{
|
|
835
855
|
name: "project_add_motion_graphic",
|
|
836
856
|
description:
|
|
837
|
-
"Drop a motion-graphic MP4/WebM onto the timeline. PREFERRED: pass `fromJob` (the jobId from motion_render_html / motion_generate / motion_concat) — the server resolves the outputPath internally, so you never handle the path string at all. Fallback: pass `file` (an absolute path) only for external/uploaded files. **Defaults to a `bundled:sound/mouse-click` SFX as the graphic appears** — pass `soundUrl: null` to make it silent, or any other `bundled:sound/<id>` to swap the sound.",
|
|
857
|
+
"Drop a motion-graphic MP4/WebM onto the timeline. PREFERRED: pass `fromJob` (the jobId from motion_render_html / motion_generate / motion_concat) — the server resolves the outputPath internally, so you never handle the path string at all. Fallback: pass `file` (an absolute path) only for external/uploaded files. Also accepts `file: 'bundled:transition/<id>'` (ids from asset_list_transitions) — the house-opener pattern: a bundled transition placed as an overlay auto-stamps transitionId so it COVER-fits any canvas (a 16:9 sweep fills a 9:16 frame) and carries its SFX via soundUrl. Image files (png/jpg/webp/gif/avif) are detected and render as image overlays; pair `file: <image>` with `layer: 'background'` for a timed BACKGROUND-IMAGE underlay (cover-fills the canvas behind the camera — with a background-effect remove region over the same span, the image replaces the speaker's real background for just that duration). **Defaults to a `bundled:sound/mouse-click` SFX as the graphic appears** — pass `soundUrl: null` to make it silent, or any other `bundled:sound/<id>` to swap the sound.",
|
|
838
858
|
inputSchema: {
|
|
839
859
|
type: "object",
|
|
840
860
|
properties: {
|
|
@@ -1203,6 +1223,48 @@ const TOOLS = [
|
|
|
1203
1223
|
},
|
|
1204
1224
|
command: "project.add-spotlight",
|
|
1205
1225
|
},
|
|
1226
|
+
{
|
|
1227
|
+
name: "project_add_background_effect",
|
|
1228
|
+
description:
|
|
1229
|
+
"Add a BACKGROUND EFFECT region on the camera video: AI person segmentation runs between startMs and endMs and either BLURS the background behind the speaker (mode 'blur' — video-call style) or REMOVES it so the project wallpaper shows through behind them (mode 'remove'). Behaves exactly like a zoom region on the timeline: draggable, trimmable, source-anchored (pass anchorSourceMs when placing from a transcript word so it survives later trims). Camera / talking-head footage only. Preview and export render it identically. Find placed regions via project_read under editor.backgroundEffectRegions[]; retime/restyle with project_update_region (regionType 'background-effect'), delete with project_remove_region.",
|
|
1230
|
+
inputSchema: {
|
|
1231
|
+
type: "object",
|
|
1232
|
+
properties: {
|
|
1233
|
+
id: { type: "string" },
|
|
1234
|
+
path: { type: "string" },
|
|
1235
|
+
atMs: { type: "number", description: "Edited-time start (ms). Or pass startMs." },
|
|
1236
|
+
startMs: { type: "number", description: "Alias for atMs (edited-time start, ms)." },
|
|
1237
|
+
endMs: { type: "number", description: "Edited-time end (ms). Alternative to durationMs." },
|
|
1238
|
+
durationMs: {
|
|
1239
|
+
type: "number",
|
|
1240
|
+
description: "Duration in ms. Default 5000 when neither durationMs nor endMs given.",
|
|
1241
|
+
},
|
|
1242
|
+
mode: {
|
|
1243
|
+
type: "string",
|
|
1244
|
+
enum: ["blur", "remove"],
|
|
1245
|
+
description:
|
|
1246
|
+
'"blur" = person sharp, background gaussian-blurred. "remove" = background cut away; the project wallpaper/background shows through behind the person.',
|
|
1247
|
+
},
|
|
1248
|
+
strength: {
|
|
1249
|
+
type: "number",
|
|
1250
|
+
description:
|
|
1251
|
+
"Blur strength in px sigma at 1080p (mode='blur' only; ignored for 'remove'). 1-120, default 18.",
|
|
1252
|
+
},
|
|
1253
|
+
anchorSourceMs: {
|
|
1254
|
+
type: "number",
|
|
1255
|
+
description:
|
|
1256
|
+
"Anchor to a SOURCE-time moment (transcript word startMs). The region then re-anchors automatically on subsequent trim/speed edits.",
|
|
1257
|
+
},
|
|
1258
|
+
anchorSourceEndMs: {
|
|
1259
|
+
type: "number",
|
|
1260
|
+
description: "Optional anchor end (source ms) for ranged anchoring.",
|
|
1261
|
+
},
|
|
1262
|
+
expectedRevision: { type: "number" },
|
|
1263
|
+
},
|
|
1264
|
+
required: ["mode"],
|
|
1265
|
+
},
|
|
1266
|
+
command: "project.add-background-effect",
|
|
1267
|
+
},
|
|
1206
1268
|
{
|
|
1207
1269
|
name: "project_update_spotlight",
|
|
1208
1270
|
description:
|
|
@@ -1281,6 +1343,60 @@ const TOOLS = [
|
|
|
1281
1343
|
},
|
|
1282
1344
|
command: "project.render-frame",
|
|
1283
1345
|
},
|
|
1346
|
+
{
|
|
1347
|
+
name: "project_render_sheet",
|
|
1348
|
+
description:
|
|
1349
|
+
"Render N preview frames sampled across an edited-time range and tile them into ONE contact-sheet PNG (row-major grid). THE verification tool for pacing and motion: one call + one image read shows caption changes, zoom ramps, overlay mutations, and dead stretches — use this instead of N sequential project_render_frame calls. Returns { sheetPath, cols, rows, frames: [{index,row,col,atMs,timeMs,path}] }; per-cell timestamps come from the frames array (cell k = frames[k], left-to-right then top-to-bottom). Every frame's seek is verified — no stale frames. If tiling is unavailable, sheetPath is null and frames[].path are readable individually.",
|
|
1350
|
+
inputSchema: {
|
|
1351
|
+
type: "object",
|
|
1352
|
+
properties: {
|
|
1353
|
+
id: { type: "string" },
|
|
1354
|
+
path: { type: "string" },
|
|
1355
|
+
fromMs: { type: "number", description: "Range start, edited ms. Default 0." },
|
|
1356
|
+
toMs: { type: "number", description: "Range end, edited ms. Default: edited end." },
|
|
1357
|
+
count: { type: "number", description: "Frames sampled evenly. Default 12, max 24." },
|
|
1358
|
+
cols: { type: "number", description: "Grid columns. Default 4." },
|
|
1359
|
+
outPath: { type: "string", description: "Optional sheet PNG output path." },
|
|
1360
|
+
},
|
|
1361
|
+
},
|
|
1362
|
+
command: "project.render-sheet",
|
|
1363
|
+
},
|
|
1364
|
+
{
|
|
1365
|
+
name: "project_apply_edit_plan",
|
|
1366
|
+
description:
|
|
1367
|
+
'Apply a whole editing plan in ONE call: a JSON array of timeline ops, applied atomically (single read, all ops, single save; any invalid op aborts with nothing written). Supported ops: {"op":"add-trim","startMs","endMs"} | {"op":"add-zoom","atMs","durationMs","depth?","focusX?","focusY?","soundUrl?","anchorSourceMs?"} | {"op":"add-speed","startMs","endMs","speed"}. ALWAYS prefer this over sequential project_add_trim/zoom/speed calls when executing a beat map — it replaces 20+ round-trips with one.',
|
|
1368
|
+
inputSchema: {
|
|
1369
|
+
type: "object",
|
|
1370
|
+
properties: {
|
|
1371
|
+
id: { type: "string" },
|
|
1372
|
+
path: { type: "string" },
|
|
1373
|
+
plan: {
|
|
1374
|
+
type: "string",
|
|
1375
|
+
description:
|
|
1376
|
+
'JSON array of ops, e.g. [{"op":"add-trim","startMs":1000,"endMs":1400},{"op":"add-zoom","atMs":5000,"durationMs":2500,"depth":2}]',
|
|
1377
|
+
},
|
|
1378
|
+
expectedRevision: { type: "number" },
|
|
1379
|
+
},
|
|
1380
|
+
required: ["plan"],
|
|
1381
|
+
},
|
|
1382
|
+
command: "project.apply-edit-plan",
|
|
1383
|
+
},
|
|
1384
|
+
{
|
|
1385
|
+
name: "audio_probe",
|
|
1386
|
+
description:
|
|
1387
|
+
'EARS without exporting: per-clip audio stats from the export-relevant track (cleanedAudioPath when present, else source). Returns hasAudio, mean/max volume (dB), detected silence spans (clip-SOURCE time), and the project audio overlays (music beds) — verify "is there dead air / is the level sane / did my music land" before export. Synchronous, a few seconds per clip.',
|
|
1388
|
+
inputSchema: {
|
|
1389
|
+
type: "object",
|
|
1390
|
+
properties: {
|
|
1391
|
+
id: { type: "string" },
|
|
1392
|
+
path: { type: "string" },
|
|
1393
|
+
clipId: { type: "string", description: "Optional — probe only this clip." },
|
|
1394
|
+
noiseDb: { type: "number", description: "Silence threshold dBFS. Default -30." },
|
|
1395
|
+
minSilenceSec: { type: "number", description: "Min silence to report, sec. Default 0.5." },
|
|
1396
|
+
},
|
|
1397
|
+
},
|
|
1398
|
+
command: "audio.probe",
|
|
1399
|
+
},
|
|
1284
1400
|
{
|
|
1285
1401
|
name: "project_set_region_sound",
|
|
1286
1402
|
description:
|
|
@@ -1460,7 +1576,8 @@ const TOOLS = [
|
|
|
1460
1576
|
},
|
|
1461
1577
|
expectedRevision: {
|
|
1462
1578
|
type: "number",
|
|
1463
|
-
description:
|
|
1579
|
+
description:
|
|
1580
|
+
"Revision from your last project_read. Strongly recommended in overwrite mode.",
|
|
1464
1581
|
},
|
|
1465
1582
|
},
|
|
1466
1583
|
},
|
|
@@ -1549,7 +1666,7 @@ const TOOLS = [
|
|
|
1549
1666
|
{
|
|
1550
1667
|
name: "project_remove_region",
|
|
1551
1668
|
description:
|
|
1552
|
-
"Delete an existing region (zoom, trim, speed, annotation, fx, overlay, clip-transform, audio-overlay) by its id. Use project_read to find region ids — visual regions live under editor.*Regions; audio overlays live under audioOverlays[]. If the region belongs to a link group (designed-segment pair: a clip-transform parking the camera into one half + the panel motion-graphic on the other half), deleting either half also removes its peer so the pair stays coherent. Returns the updated project.",
|
|
1669
|
+
"Delete an existing region (zoom, trim, speed, annotation, fx, overlay, clip-transform, background-effect, audio-overlay) by its id. Use project_read to find region ids — visual regions live under editor.*Regions; audio overlays live under audioOverlays[]. If the region belongs to a link group (designed-segment pair: a clip-transform parking the camera into one half + the panel motion-graphic on the other half), deleting either half also removes its peer so the pair stays coherent. Returns the updated project.",
|
|
1553
1670
|
inputSchema: {
|
|
1554
1671
|
type: "object",
|
|
1555
1672
|
properties: {
|
|
@@ -1558,7 +1675,7 @@ const TOOLS = [
|
|
|
1558
1675
|
regionType: {
|
|
1559
1676
|
type: "string",
|
|
1560
1677
|
description:
|
|
1561
|
-
"zoom | trim | speed | annotation | fx | overlay | clip-transform | audio-overlay",
|
|
1678
|
+
"zoom | trim | speed | annotation | fx | overlay | clip-transform | background-effect | audio-overlay",
|
|
1562
1679
|
},
|
|
1563
1680
|
regionId: { type: "string", description: "id of the region to delete" },
|
|
1564
1681
|
expectedRevision: { type: "number" },
|
|
@@ -1589,7 +1706,7 @@ const TOOLS = [
|
|
|
1589
1706
|
{
|
|
1590
1707
|
name: "project_update_region",
|
|
1591
1708
|
description:
|
|
1592
|
-
"Patch fields on an existing region without replacing it. Accepts any subset of the region's own properties — only the supplied fields are changed, everything else is preserved. Works on zoom (depth, focusX, focusY, focusMode: 'static'|'auto' — set 'auto' for the cursor-follow camera), trim (startMs, endMs), speed (startMs, endMs, speed), fx (atMs, durationMs, opacity, blendMode, speed), annotation, overlay, clip-transform (startMs, endMs, preset, transitionMs), and audio-overlay (startMs, endMs, sourceStartMs, volume — lets you drag-trim an audio overlay without removing it). Time shifts on a link-grouped region (e.g. one half of a designed segment) propagate to every peer in the group so the pair stays coherent.",
|
|
1709
|
+
"Patch fields on an existing region without replacing it. Accepts any subset of the region's own properties — only the supplied fields are changed, everything else is preserved. Overlay geometry (x/y/width/height) is PERCENT of canvas 0-100; values <= 1 are treated as 0-1 fractions and scaled. Works on zoom (depth, focusX, focusY, focusMode: 'static'|'auto' — set 'auto' for the cursor-follow camera), trim (startMs, endMs), speed (startMs, endMs, speed), fx (atMs, durationMs, opacity, blendMode, speed), annotation, overlay, clip-transform (startMs, endMs, preset, transitionMs), background-effect (startMs, endMs, mode: 'blur'|'remove', strength), and audio-overlay (startMs, endMs, sourceStartMs, volume — lets you drag-trim an audio overlay without removing it). Time shifts on a link-grouped region (e.g. one half of a designed segment) propagate to every peer in the group so the pair stays coherent.",
|
|
1593
1710
|
inputSchema: {
|
|
1594
1711
|
type: "object",
|
|
1595
1712
|
properties: {
|
|
@@ -1598,7 +1715,7 @@ const TOOLS = [
|
|
|
1598
1715
|
regionType: {
|
|
1599
1716
|
type: "string",
|
|
1600
1717
|
description:
|
|
1601
|
-
"zoom | trim | speed | annotation | fx | overlay | clip-transform | audio-overlay",
|
|
1718
|
+
"zoom | trim | speed | annotation | fx | overlay | clip-transform | background-effect | audio-overlay",
|
|
1602
1719
|
},
|
|
1603
1720
|
regionId: { type: "string", description: "id of the region to update" },
|
|
1604
1721
|
patch: {
|
|
@@ -2012,7 +2129,7 @@ const TOOLS = [
|
|
|
2012
2129
|
{
|
|
2013
2130
|
name: "transcript_get",
|
|
2014
2131
|
description:
|
|
2015
|
-
"Return the merged
|
|
2132
|
+
"Return the merged transcript. Each word carries BOTH time bases: startMs/endMs are SOURCE time (the raw recording; use for anchorSourceMs), and editedStartMs/editedEndMs are EDITED-timeline time with trims/speeds applied (null = the word sits inside a trim). Read the base you need; no conversion calls required. Word IDs feed transcript_delete_words. For a PODCAST composite, each word also carries `speaker` ('host' = speaker 1 / mediaPath, 'guest' = speaker 2 / webcamPath) — use these spans to drive project_set_clip_layout (cut to whoever is talking).",
|
|
2016
2133
|
inputSchema: {
|
|
2017
2134
|
type: "object",
|
|
2018
2135
|
properties: {
|
|
@@ -2100,7 +2217,7 @@ const TOOLS = [
|
|
|
2100
2217
|
{
|
|
2101
2218
|
name: "transcript_find_issues",
|
|
2102
2219
|
description:
|
|
2103
|
-
"Scan the transcript for editorial problems the speaker created and recovered from: **duplicate-take** (a line re-recorded later — keep the cleaner second take, cut the first), **false-start** (an abandoned fragment before a restart), and **adjacent-repeat** (a stutter like 'the the'). Returns candidate `issues`, each with `type`, `severity`, the `wordIds` of the DISCARDED attempt (pass straight to transcript.delete-words), the edited-time span, the offending `text`, and a `note` explaining what's kept. CONSERVATIVE and read-only — it never edits
|
|
2220
|
+
"Scan the transcript for editorial problems the speaker created and recovered from: **duplicate-take** (a line re-recorded later — keep the cleaner second take, cut the first), **false-start** (an abandoned fragment before a restart), and **adjacent-repeat** (a stutter like 'the the'). Returns candidate `issues`, each with `type`, `severity`, the `wordIds` of the DISCARDED attempt (pass straight to transcript.delete-words), the edited-time span, the offending `text`, and a `note` explaining what's kept. CONSERVATIVE and read-only — it never edits. Deliberate parallel structure ('one for transcription, one for outreach') is NOT flagged as a false start, and lone stopword repeats across pause tokens are skipped. A `severity: \"low\"` false-start is a REVIEW candidate (the restart diverges from the fragment — possibly an intentional list): KEEP it by default unless context clearly shows a flubbed take. Review every candidate against context before deleting. Run this after transcribe + remove-fillers as the content-cleanup pass. Filter with `types` (comma-separated) and cap with `limit`.",
|
|
2104
2221
|
inputSchema: {
|
|
2105
2222
|
type: "object",
|
|
2106
2223
|
properties: {
|
|
@@ -2119,7 +2236,7 @@ const TOOLS = [
|
|
|
2119
2236
|
{
|
|
2120
2237
|
name: "transcript_find_replace",
|
|
2121
2238
|
description:
|
|
2122
|
-
"Find
|
|
2239
|
+
"Find every occurrence of a phrase in the transcript and replace it with new text. Useful for correcting mis-transcribed names, technical terms, or brand names without re-transcribing. Timing is preserved — only the displayed/exported text changes. Returns { replacedCount, wordsPatched }; replacedCount is 0 if the phrase wasn't found.",
|
|
2123
2240
|
inputSchema: {
|
|
2124
2241
|
type: "object",
|
|
2125
2242
|
properties: {
|
|
@@ -2128,7 +2245,7 @@ const TOOLS = [
|
|
|
2128
2245
|
find: {
|
|
2129
2246
|
type: "string",
|
|
2130
2247
|
description:
|
|
2131
|
-
'Phrase to search for. Case-insensitive;
|
|
2248
|
+
'Phrase to search for. Case-insensitive; punctuation is ignored. Digits are SIGNIFICANT when the phrase contains them ("try30" matches only the token "try30") and ignored otherwise ("than" also matches "than60"). A multi-word phrase also matches a single merged STT token ("Wispr Flow" as one token; "of $499" matches "of$499"), and a phrase equal to a token\'s raw text always matches — so space/digit/punctuation-bearing tokens (even pure numbers like "30%") are all targetable.',
|
|
2132
2249
|
},
|
|
2133
2250
|
replace: { type: "string", description: "Replacement text (replaces the whole phrase)" },
|
|
2134
2251
|
expectedRevision: { type: "number" },
|
|
@@ -2222,7 +2339,7 @@ const TOOLS = [
|
|
|
2222
2339
|
{
|
|
2223
2340
|
name: "caption_set_style",
|
|
2224
2341
|
description:
|
|
2225
|
-
"Override specific style properties of the caption overlay without changing the template. All fields are optional — pass only what you want to change. positionY controls vertical placement (0=top, 100=bottom, default 85). fontFamily sets the caption font (a system font like 'Georgia' or a loaded custom font; unloaded fonts fall back). fontSize is in REM (e.g. '2.6rem'), matching the editor's Font size slider; it is clamped to 1.0–5.0rem (a px value is converted to rem), so you can't exceed the editor's max.",
|
|
2342
|
+
"Override specific style properties of the caption overlay without changing the template. All fields are optional — pass only what you want to change. positionY controls vertical placement in PERCENT of frame height (0=top, 100=bottom, default 85; a 0-1 fraction like 0.85 is auto-converted to percent). fontFamily sets the caption font (a system font like 'Georgia' or a loaded custom font; unloaded fonts fall back). fontSize is in REM (e.g. '2.6rem'), matching the editor's Font size slider; it is clamped to 1.0–5.0rem (a px value is converted to rem), so you can't exceed the editor's max.",
|
|
2226
2343
|
inputSchema: {
|
|
2227
2344
|
type: "object",
|
|
2228
2345
|
properties: {
|
|
@@ -2232,7 +2349,11 @@ const TOOLS = [
|
|
|
2232
2349
|
type: "number",
|
|
2233
2350
|
description: "Max words shown at once. Default varies by template.",
|
|
2234
2351
|
},
|
|
2235
|
-
positionY: {
|
|
2352
|
+
positionY: {
|
|
2353
|
+
type: "number",
|
|
2354
|
+
description:
|
|
2355
|
+
"Vertical position as percent of frame height from the top (0-100). Default 85. Fractions \u2264 1.0 (e.g. 0.85) are treated as 0-1 and converted to percent.",
|
|
2356
|
+
},
|
|
2236
2357
|
fontFamily: {
|
|
2237
2358
|
type: "string",
|
|
2238
2359
|
description:
|
|
@@ -2384,7 +2505,7 @@ const TOOLS = [
|
|
|
2384
2505
|
{
|
|
2385
2506
|
name: "motion_generate",
|
|
2386
2507
|
description:
|
|
2387
|
-
"Render a bundled template by id with your own slot values (text, colors, list items) and a background mode, then add the result to the timeline with project_add_motion_graphic (or project_add_designed_segment for split-panel). Async — returns { jobId, outputPath }; call job_wait, then pass that jobId as `fromJob` to the add tool. Discover templates + slots with motion_list. Prefer this over motion_render_html whenever a bundled template fits the brief — it's faster and already designed.",
|
|
2508
|
+
"Render a bundled template by id with your own slot values (text, colors, list items) and a background mode, then add the result to the timeline with project_add_motion_graphic (or project_add_designed_segment for split-panel). Async — returns { jobId, outputPath }; call job_wait, then pass that jobId as `fromJob` to the add tool. Discover templates + slots with motion_list. Prefer this over motion_render_html whenever a bundled template fits the brief — it's faster and already designed. The same automatic quality gates as motion_render_html apply (pre-render contract lint; STATIC_RENDER on frozen output).",
|
|
2388
2509
|
inputSchema: {
|
|
2389
2510
|
type: "object",
|
|
2390
2511
|
properties: {
|
|
@@ -2457,7 +2578,7 @@ const TOOLS = [
|
|
|
2457
2578
|
{
|
|
2458
2579
|
name: "motion_render_html",
|
|
2459
2580
|
description:
|
|
2460
|
-
"Render arbitrary HTML/CSS/JS to video via the HyperFrames engine — frame-perfect, seekable capture through chrome-headless-shell's BeginFrame API. Use this when the bundled motion-graphic templates don't fit the brief. The HTML MUST expose a paused GSAP timeline registered as `window.__timelines[<data-composition-id>] = tl` — NOT CSS keyframes, NOT setTimeout, NOT `window.__hf.seek` (that lower-level protocol skips the compositor invalidation wrapper and renders with 1-second stalls). See the SKILL.md `Custom motion graphics — HTML authoring` section for the required `data-composition-id`/`data-width`/`data-height`/`data-duration` root element, the canonical template, and pacing rules. Pass either inline `html` or `htmlPath`. Set `transparent: true` for a WebM+alpha overlay (lower thirds, watermarks).
|
|
2581
|
+
"Render arbitrary HTML/CSS/JS to video via the HyperFrames engine — frame-perfect, seekable capture through chrome-headless-shell's BeginFrame API. Use this when the bundled motion-graphic templates don't fit the brief. The HTML MUST expose a paused GSAP timeline registered as `window.__timelines[<data-composition-id>] = tl` — NOT CSS keyframes, NOT setTimeout, NOT `window.__hf.seek` (that lower-level protocol skips the compositor invalidation wrapper and renders with 1-second stalls). See the SKILL.md `Custom motion graphics — HTML authoring` section for the required `data-composition-id`/`data-width`/`data-height`/`data-duration` root element, the canonical template, and pacing rules. Pass either inline `html` or `htmlPath`. Set `transparent: true` for a WebM+alpha overlay (lower thirds, watermarks). Renders queue automatically and execute serially in submission order — fire multiple renders back-to-back without waiting, then job_wait each. RENDER_BUSY appears only past a 16-deep runaway ceiling. Two automatic quality gates apply: a pre-render lint rejects contract violations (unpaused timeline, repeat:-1, Math.random, missing __timelines registration, shadow/blur radii over 500px), and a post-render check fails any clip frozen for 90%+ of its duration with STATIC_RENDER (fix the composition; never place a gated clip). Async — returns { jobId, outputPath }; poll job_wait for the final file.",
|
|
2461
2582
|
inputSchema: {
|
|
2462
2583
|
type: "object",
|
|
2463
2584
|
properties: {
|
|
@@ -2933,7 +3054,7 @@ const TOOLS = [
|
|
|
2933
3054
|
const server = new Server(
|
|
2934
3055
|
{
|
|
2935
3056
|
name: "pandastudio",
|
|
2936
|
-
version: "1.
|
|
3057
|
+
version: "1.72.0",
|
|
2937
3058
|
},
|
|
2938
3059
|
{
|
|
2939
3060
|
capabilities: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@writepanda/mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.99.0",
|
|
4
4
|
"description": "Model Context Protocol server for PandaStudio. Exposes the desktop video editor's automation surface to Cursor, Continue, Cline, Claude Desktop, and any MCP-compliant client.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pandastudio",
|