@writepanda/mcp 1.224.0 → 1.226.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/README.md CHANGED
@@ -71,6 +71,7 @@ Restart the client afterwards. The server auto-launches PandaStudio if it isn't
71
71
  | `caption_*` | 3 | Toggle, templates, per-style overrides |
72
72
  | `media_*` | 3 | Generate images, music, narration |
73
73
  | `preview_*` / `job_*` / `agent_*` / `audio_*` / `timeline_*` | 14 | Live preview overlay, async job polling, agent sessions, audio cleanup, time-base conversion |
74
+ | `review_*` | 5 | The pins, drawings and picked graphics the user marked on the preview: list them (with frame crops), resolve them when done, add / update / delete |
74
75
 
75
76
  Plus `pandastudio_call` — a raw `verb.noun` escape hatch for anything not yet a first-class tool.
76
77
 
package/bin/server.mjs CHANGED
@@ -1871,6 +1871,15 @@ const TOOLS = [
1871
1871
  path: { type: "string" },
1872
1872
  overlayId: { type: "string" },
1873
1873
  slots: { type: "object", description: "Templates: changed slots only." },
1874
+ variables: {
1875
+ type: "object",
1876
+ description:
1877
+ "Typed values by id (motion_variables lists them). Validated by type/range/step/options; template slots and HyperFrames HTML declarations alike. Images embedded.",
1878
+ },
1879
+ preset: {
1880
+ type: "string",
1881
+ description: "Saved variable preset name (motion_list_presets); variables apply on top.",
1882
+ },
1874
1883
  background: { type: "string", enum: ["solid", "transparent", "glass"] },
1875
1884
  html: { type: "string", description: "HTML graphics: full replacement." },
1876
1885
  durationMs: { type: "number", description: "text-behind length." },
@@ -2177,7 +2186,7 @@ const TOOLS = [
2177
2186
  {
2178
2187
  name: "project_add_3d_captions",
2179
2188
  description:
2180
- "3D camera captions on a talking-head HOOK (5-20 s, 16:9, locked medium shot): the camera whips in and trucks between caption groups at different depths, a hero word hides behind the head, an optional ring of words turns in front of the speaker, a staircase finale. Rendered from the clip's footage, matte and transcript; placed over [startMs, endMs] (editor captions hidden there). Do it after the cuts. Refuses a moving camera or extreme close-up. ASYNC: job_wait gives overlayId, beats[].still (look at each), qc, warnings. Re-run replaces; reference/3d-captions.md.",
2189
+ "3D camera captions on a talking-head HOOK (5-20 s, 16:9 or vertical 9:16, also 1:1 / 4:5; locked medium shot; in 9:16 built from the crop the Short shows, laid out inside the Shorts-safe area): the camera whips in and trucks between caption groups at different depths, a hero word hides behind the head, an optional ring of words turns in front of the speaker, a staircase finale. Rendered from the clip's footage, matte and transcript; placed over [startMs, endMs] (editor captions hidden there). Do it after the cuts. Refuses a moving camera or extreme close-up. ASYNC: job_wait gives overlayId, beats[].still (look at each), qc, warnings. Re-run replaces; reference/3d-captions.md.",
2181
2190
  inputSchema: {
2182
2191
  type: "object",
2183
2192
  properties: {
@@ -3104,6 +3113,28 @@ const TOOLS = [
3104
3113
  },
3105
3114
  command: "project.add-freeze-frame",
3106
3115
  },
3116
+ {
3117
+ name: "project_close_gap",
3118
+ description:
3119
+ "Close a gap on the timeline (the editor's right-click Close gap), one undo step. Times are EDITED ms (project_read / timeline_source_to_edited). lane main (default): ripple-delete startMs..endMs from the video; later content, captions and anchored graphics move back with their words; inserted pauses in the range are shortened or removed. lane media[:i] / audio[:i] / effects[:i] (zero-based sub-lane, packed by start time like the timeline rows): the range must be empty in that lane; every later item in that lane shifts left by the gap. Speed changes never move.",
3120
+ inputSchema: {
3121
+ type: "object",
3122
+ properties: {
3123
+ id: { type: "string", description: "Project id (or pass path)." },
3124
+ path: { type: "string", description: "Project file path (or pass id)." },
3125
+ startMs: { type: "number", description: "Gap start, edited ms." },
3126
+ endMs: { type: "number", description: "Gap end, edited ms (> startMs)." },
3127
+ lane: {
3128
+ type: "string",
3129
+ description: "main (default), or media[:i], audio[:i], effects[:i] for a sub-lane.",
3130
+ },
3131
+ expectedRevision: { type: "number" },
3132
+ },
3133
+ required: ["startMs", "endMs"],
3134
+ },
3135
+ command: "project.close-gap",
3136
+ },
3137
+
3107
3138
  {
3108
3139
  name: "project_remove_pause",
3109
3140
  description:
@@ -3376,7 +3407,7 @@ const TOOLS = [
3376
3407
  {
3377
3408
  name: "project_remove_region",
3378
3409
  description:
3379
- "Delete a region by id (editor.*Regions, motion-element = editor.motionElements, or audioOverlays); linked peers go too.",
3410
+ "Delete a region by id (editor.*Regions, motion-element = editor.motionElements, or audioOverlays); only this region is removed by default. Pass group=true to also remove linked peers.",
3380
3411
  inputSchema: {
3381
3412
  type: "object",
3382
3413
  properties: {
@@ -3384,6 +3415,7 @@ const TOOLS = [
3384
3415
  path: { type: "string" },
3385
3416
  regionType: { type: "string", enum: [...REGION_TYPES, "motion-element"] },
3386
3417
  regionId: { type: "string" },
3418
+ group: { type: "boolean", description: "Remove all linked peers too. Default false." },
3387
3419
  expectedRevision: { type: "number" },
3388
3420
  },
3389
3421
  required: ["regionType", "regionId"],
@@ -4111,6 +4143,97 @@ const TOOLS = [
4111
4143
  },
4112
4144
  command: "project.remove-volume-keyframe",
4113
4145
  },
4146
+ {
4147
+ name: "project_set_audio_carve",
4148
+ description:
4149
+ "Voiceover carve on a music bed (audio overlay): while the main voice or a voiceover speaks, dip the bed's speech band (default 1000-4000 Hz) instead of lowering the whole track. amount 0..1 = deepest cut 2..18 dB (default 0.25 = 6 dB). Combine with a lighter project_set_audio_ducking. Omitted fields keep their value. Export accurate; the editor preview does not carve.",
4150
+ inputSchema: {
4151
+ type: "object",
4152
+ properties: {
4153
+ id: { type: "string" },
4154
+ path: { type: "string" },
4155
+ trackId: { type: "string", description: "The music bed's audio overlay id, e.g. audio-1." },
4156
+ enabled: { type: "boolean", description: "Carve on (default) or off." },
4157
+ amount: { type: "number", minimum: 0, maximum: 1 },
4158
+ lowHz: {
4159
+ type: "number",
4160
+ minimum: 160,
4161
+ maximum: 6000,
4162
+ description: "Band low edge (default 1000).",
4163
+ },
4164
+ highHz: {
4165
+ type: "number",
4166
+ minimum: 160,
4167
+ maximum: 6000,
4168
+ description: "Band high edge, above lowHz (default 4000).",
4169
+ },
4170
+ remove: { type: "boolean", description: "Delete the carve settings." },
4171
+ expectedRevision: { type: "number" },
4172
+ },
4173
+ required: ["trackId"],
4174
+ },
4175
+ command: "project.set-audio-carve",
4176
+ },
4177
+ {
4178
+ name: "project_set_audio_fx",
4179
+ description:
4180
+ 'Replace a track\'s ordered FX rack. trackId = audio overlay id or "main" (the main voice, one rack for all clips). fx = [{type, enabled?, params?}], [] clears. Types (params, ranges): highpass/lowpass (frequency Hz, q 0.1-20, poles "1"|"2"); lowshelf/highshelf (frequency, gain -40..40 dB); peaking (frequency, gain, q; a 3-band EQ = three peaking at 200/1000/4000 Hz); compressor (threshold -60..0 dB, ratio 1-20, attack 0.01-2000 ms, release 0.01-9000 ms, knee 1-8, makeup 0-36 dB, mix 0-1); deesser (intensity 0-1, frequency 0-1); reverb (size 0.05-1, damping 0-1, wet 0-1, dry 0-1); gain (gain -60..12 dB). Export applies all; preview: overlays hear EQ/gain/reverb, compressor approximated, de-esser export only; main voice previews gain only.',
4181
+ inputSchema: {
4182
+ type: "object",
4183
+ properties: {
4184
+ id: { type: "string" },
4185
+ path: { type: "string" },
4186
+ trackId: { type: "string", description: 'Audio overlay id, or "main".' },
4187
+ main: { type: "boolean", description: 'Same as trackId "main".' },
4188
+ fx: {
4189
+ type: "array",
4190
+ items: {
4191
+ type: "object",
4192
+ properties: {
4193
+ type: {
4194
+ type: "string",
4195
+ enum: [
4196
+ "highpass",
4197
+ "lowpass",
4198
+ "lowshelf",
4199
+ "highshelf",
4200
+ "peaking",
4201
+ "compressor",
4202
+ "deesser",
4203
+ "reverb",
4204
+ "gain",
4205
+ ],
4206
+ },
4207
+ enabled: { type: "boolean" },
4208
+ params: { type: "object" },
4209
+ },
4210
+ required: ["type"],
4211
+ },
4212
+ },
4213
+ expectedRevision: { type: "number" },
4214
+ },
4215
+ required: ["fx"],
4216
+ },
4217
+ command: "project.set-audio-fx",
4218
+ },
4219
+ {
4220
+ name: "project_set_audio_buses",
4221
+ description:
4222
+ "Mix group gains in dB (-60..12): voice (main track, voiceovers, overlay video sound), music (music beds), sfx (sound effects). Multiplies every track in the group after its own volume, ducking and FX. Omitted buses keep their value; reset sets all to 0 first. Preview and export match (preview caps each track at 100%).",
4223
+ inputSchema: {
4224
+ type: "object",
4225
+ properties: {
4226
+ id: { type: "string" },
4227
+ path: { type: "string" },
4228
+ voice: { type: "number", minimum: -60, maximum: 12 },
4229
+ music: { type: "number", minimum: -60, maximum: 12 },
4230
+ sfx: { type: "number", minimum: -60, maximum: 12 },
4231
+ reset: { type: "boolean" },
4232
+ expectedRevision: { type: "number" },
4233
+ },
4234
+ },
4235
+ command: "project.set-audio-buses",
4236
+ },
4114
4237
  {
4115
4238
  name: "project_set_audio_ducking",
4116
4239
  description:
@@ -5560,7 +5683,7 @@ const TOOLS = [
5560
5683
  {
5561
5684
  name: "motion_list",
5562
5685
  description:
5563
- "List motion-graphic templates (slots, family, tags; render with motion_generate) and slot-less registryBlocks (edit htmlPath, render with motion_render_html). Retired templates are hidden unless includeRetired.",
5686
+ "List motion-graphic templates (each with typed variables: type, default, min/max/step, options; plus slots, family, tags) and registryBlocks (with any declared variables). Render templates with motion_generate, blocks with motion_render_html. Retired templates are hidden unless includeRetired.",
5564
5687
  inputSchema: {
5565
5688
  type: "object",
5566
5689
  properties: {
@@ -5578,6 +5701,58 @@ const TOOLS = [
5578
5701
  },
5579
5702
  command: "motion.list",
5580
5703
  },
5704
+ {
5705
+ name: "motion_variables",
5706
+ description:
5707
+ "Typed variables of a graphic (text, multiline, number, color, image, font, boolean, enum, list) with defaults/constraints, its current values and preset templateKey. source = an overlay's generatedFrom from project_read (template or HyperFrames HTML).",
5708
+ inputSchema: {
5709
+ type: "object",
5710
+ properties: { source: { type: "object" } },
5711
+ required: ["source"],
5712
+ },
5713
+ command: "motion.variables",
5714
+ },
5715
+ {
5716
+ name: "motion_list_presets",
5717
+ description:
5718
+ "List the active workspace's saved variable presets {name, templateKey, values}. Pass source or templateId to list only those for that graphic.",
5719
+ inputSchema: {
5720
+ type: "object",
5721
+ properties: { source: { type: "object" }, templateId: { type: "string" } },
5722
+ },
5723
+ command: "motion.list-presets",
5724
+ },
5725
+ {
5726
+ name: "motion_save_preset",
5727
+ description:
5728
+ "Save a named variable set (e.g. 'Client A lower third') for graphics of the same template in this workspace; same name replaces. Images are embedded.",
5729
+ inputSchema: {
5730
+ type: "object",
5731
+ properties: {
5732
+ source: { type: "object" },
5733
+ name: { type: "string" },
5734
+ variables: { type: "object" },
5735
+ },
5736
+ required: ["source", "name", "variables"],
5737
+ },
5738
+ command: "motion.save-preset",
5739
+ },
5740
+ {
5741
+ name: "motion_delete_preset",
5742
+ description:
5743
+ "Delete a saved variable preset by name; identify its template with source, templateId or templateKey.",
5744
+ inputSchema: {
5745
+ type: "object",
5746
+ properties: {
5747
+ name: { type: "string" },
5748
+ source: { type: "object" },
5749
+ templateId: { type: "string" },
5750
+ templateKey: { type: "string" },
5751
+ },
5752
+ required: ["name"],
5753
+ },
5754
+ command: "motion.delete-preset",
5755
+ },
5581
5756
  {
5582
5757
  name: "motion_generate",
5583
5758
  description:
@@ -5587,6 +5762,15 @@ const TOOLS = [
5587
5762
  properties: {
5588
5763
  templateId: { type: "string" },
5589
5764
  slots: { type: "object", description: "By key; image slots take a file path." },
5765
+ variables: {
5766
+ type: "object",
5767
+ description:
5768
+ "Typed values by id (templates[].variables from motion_list), validated, overriding slots. Images: path, URL or {url}; fonts: name or {name}.",
5769
+ },
5770
+ preset: {
5771
+ type: "string",
5772
+ description: "Saved variable preset name for this template (motion_list_presets).",
5773
+ },
5590
5774
  aspectRatio: { type: "string", enum: ["16:9", "9:16", "1:1"] },
5591
5775
  background: {
5592
5776
  type: "string",
@@ -5599,7 +5783,7 @@ const TOOLS = [
5599
5783
  description: "Length for templates with a durationRangeMs (clamped); ignored otherwise.",
5600
5784
  },
5601
5785
  },
5602
- required: ["templateId", "slots"],
5786
+ required: ["templateId"],
5603
5787
  },
5604
5788
  command: "motion.generate",
5605
5789
  },
@@ -5642,7 +5826,8 @@ const TOOLS = [
5642
5826
  },
5643
5827
  {
5644
5828
  name: "motion_catalog_item",
5645
- description: "One catalog item: variables, htmlPath, demoPath and a mount snippet.",
5829
+ description:
5830
+ "One catalog item: variables (raw and typedVariables), htmlPath, demoPath and a mount snippet.",
5646
5831
  inputSchema: {
5647
5832
  type: "object",
5648
5833
  properties: { name: { type: "string" } },
@@ -5700,6 +5885,23 @@ const TOOLS = [
5700
5885
  },
5701
5886
  command: "motion.render-film",
5702
5887
  },
5888
+ {
5889
+ name: "motion_edit_element",
5890
+ description:
5891
+ 'Edit one graphic element, write stored HTML and re-render in place with the same timeline timing. Async jobId. selector: #id or [data-panda-element="handle"]. patch: text, x/y (paired translation in px), width/height (px), rotation (degrees), layer (z-index), styles (color, background-color, font-family, font-size, font-weight, border-radius, opacity). Mapped text-only edits preserve template slots; structural edits become custom HTML.',
5892
+ inputSchema: {
5893
+ type: "object",
5894
+ properties: {
5895
+ id: { type: "string" },
5896
+ regionId: { type: "string" },
5897
+ selector: { type: "string" },
5898
+ patch: { type: "object" },
5899
+ expectedRevision: { type: "number" },
5900
+ },
5901
+ required: ["id", "regionId", "selector", "patch"],
5902
+ },
5903
+ command: "motion.edit-element",
5904
+ },
5703
5905
  {
5704
5906
  name: "motion_render_html",
5705
5907
  description:
@@ -6363,6 +6565,83 @@ const TOOLS = [
6363
6565
  command: "job.get",
6364
6566
  },
6365
6567
 
6568
+ // ── review items (pins / drawings the user placed on the preview) ──
6569
+ {
6570
+ name: "review_list",
6571
+ description:
6572
+ "The review items the user marked on the preview (numbered pins and drawings, each with the graphic or element under it), open ones by default. Each: id, timeMs and optional range (edited ms), position {x,y} (0..1 of the frame), drawing points, targetIds (graphic / element / region / clip ids), note, sentAt, and imagePath: a PNG crop of the frame with the drawing burned in. Defaults to the open project.",
6573
+ inputSchema: {
6574
+ type: "object",
6575
+ properties: {
6576
+ id: { type: "string", description: "Project id. Omit for the open project." },
6577
+ path: { type: "string" },
6578
+ includeResolved: { type: "boolean", description: "Also list resolved items." },
6579
+ },
6580
+ },
6581
+ command: "review.list",
6582
+ },
6583
+ {
6584
+ name: "review_resolve",
6585
+ description:
6586
+ "Mark review items done after you applied them: their pins leave the preview and the Pending edits tray. Needs the project id (or path) and the item ids.",
6587
+ inputSchema: {
6588
+ type: "object",
6589
+ properties: {
6590
+ id: { type: "string" },
6591
+ path: { type: "string" },
6592
+ annotationIds: { type: "array", items: { type: "string" } },
6593
+ },
6594
+ required: ["annotationIds"],
6595
+ },
6596
+ command: "review.resolve",
6597
+ },
6598
+
6599
+ {
6600
+ name: "review_add",
6601
+ description:
6602
+ "Add a review item (numbered pin) to the project. annotation: { timeMs (edited ms), position {x,y} 0..1, note, kind comment|freehand, targetIds?, range? {startMs,endMs}, drawing? [{x,y}], id? }.",
6603
+ inputSchema: {
6604
+ type: "object",
6605
+ properties: {
6606
+ id: { type: "string" },
6607
+ path: { type: "string" },
6608
+ annotation: { type: "object" },
6609
+ },
6610
+ required: ["annotation"],
6611
+ },
6612
+ command: "review.add",
6613
+ },
6614
+ {
6615
+ name: "review_update",
6616
+ description:
6617
+ "Change a review item's fields (note, range, position, targetIds, resolved); other fields are kept.",
6618
+ inputSchema: {
6619
+ type: "object",
6620
+ properties: {
6621
+ id: { type: "string" },
6622
+ path: { type: "string" },
6623
+ annotationId: { type: "string" },
6624
+ annotation: { type: "object", description: "Fields to change." },
6625
+ },
6626
+ required: ["annotationId", "annotation"],
6627
+ },
6628
+ command: "review.update",
6629
+ },
6630
+ {
6631
+ name: "review_delete",
6632
+ description: "Delete a review item. Use review_resolve for items you completed.",
6633
+ inputSchema: {
6634
+ type: "object",
6635
+ properties: {
6636
+ id: { type: "string" },
6637
+ path: { type: "string" },
6638
+ annotationId: { type: "string" },
6639
+ },
6640
+ required: ["annotationId"],
6641
+ },
6642
+ command: "review.delete",
6643
+ },
6644
+
6366
6645
  // ── timeline helpers ────────────────────────────────────────────
6367
6646
  {
6368
6647
  name: "timeline_source_to_edited",
@@ -6394,6 +6673,35 @@ const TOOLS = [
6394
6673
  command: "timeline.edited-to-source",
6395
6674
  },
6396
6675
 
6676
+ {
6677
+ name: "agent_run_list",
6678
+ description:
6679
+ "List a project's agent runs that changed it (newest last): runId, sessionId, prompt, and whether each is currently undone.",
6680
+ inputSchema: {
6681
+ type: "object",
6682
+ properties: {
6683
+ projectId: { type: "string" },
6684
+ sessionId: { type: "string", description: "Only runs from this chat" },
6685
+ },
6686
+ required: ["projectId"],
6687
+ },
6688
+ command: "agent.run-list",
6689
+ },
6690
+ {
6691
+ name: "agent_run_restore",
6692
+ description:
6693
+ "Undo an agent run (restore the whole project as it was before the run), or redo it when it is undone. If edits were made after the run it changes nothing and answers confirmationRequired with laterEdits and a revision: show the user what would be lost, and only after they agree call again with confirmRevision set to that revision.",
6694
+ inputSchema: {
6695
+ type: "object",
6696
+ properties: {
6697
+ projectId: { type: "string" },
6698
+ runId: { type: "string" },
6699
+ confirmRevision: { type: "number" },
6700
+ },
6701
+ required: ["projectId", "runId"],
6702
+ },
6703
+ command: "agent.run-restore",
6704
+ },
6397
6705
  ...["read", "search"].map((action) => ({
6398
6706
  name: `agent_session_${action}`,
6399
6707
  description:
@@ -6469,12 +6777,16 @@ Before an editing pass, read the skill's "Which tool for which moment" section (
6469
6777
  Report the real count each step returned. No music, intro/outro cards or FX overlays unless the recipe or the user asks for them.
6470
6778
  A single named operation ("just add captions", "remove the silences") is exactly that: no recipe.
6471
6779
 
6780
+ End every turn that changed a project with a plain summary: what changed (with the counts the tools returned), what you tried and put back or skipped and why, and what is left. Say so plainly when the work is only partly done or a step failed. To take a run back: agent_run_restore (works from inside your current turn), or the chat's Undo this run button.
6781
+
6472
6782
  Destructive actions need the user's own confirmation: project_delete, workspace_delete, recipe_delete, memory_forget, export_delete, export_clear_thumbnail, youtube_disconnect, social_disconnect. Ask plainly in chat first ("Delete project 'Demo'? This can't be undone.") and call only after they agree. PandaStudio then confirms with them itself: in its own chat the call returns confirmation_required and changes nothing while a Delete / Cancel card waits (tell them to press it; don't call again; the outcome arrives as their next message); from other apps a dialog asks, and the call is refused when nobody is at the app. Nothing you send can approve it.
6473
6783
 
6474
6784
  Publishing has daily limits (YouTube uploads and updates per license, social posts per workspace, and PandaStudio's shared YouTube quota). A failure with code daily_limit or youtube_quota_exceeded carries resetsAt: tell the user when it resets and do not retry, loop or switch accounts to get around it before then. It is not a bug to report.
6475
6785
 
6476
6786
  PandaStudio problems (a capability gap like an imported font the export can't draw, a command failing unexpectedly, a render not matching the project): call system_report_issue yourself, then tell the user you reported it. Not for user mistakes, missing keys or taste.
6477
6787
 
6788
+ Review items: the user can pin notes and drawings on the preview and send them in one turn (listed in the editor context and the message, crops attached in item order). Apply each one, then review_resolve with its id. review_list shows the open ones with a crop image path.
6789
+
6478
6790
  Destination decides everything: infer it (Shorts/TikTok/vertical/<=90s: shorts; YouTube/tutorial/>8 min: youtube-long; LinkedIn; Loom/internal), else ask once.
6479
6791
 
6480
6792
  Full playbook (per-destination defaults, motion graphics, recipes, publishing): the PandaStudio skill. If you can't load it as a skill, call skill_read with no arguments at the start, then read the sections and reference docs the task needs. The rules above are only a summary.`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@writepanda/mcp",
3
- "version": "1.224.0",
3
+ "version": "1.226.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",