@writepanda/mcp 1.210.0 → 1.213.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.
Files changed (2) hide show
  1. package/bin/server.mjs +183 -16
  2. package/package.json +1 -1
package/bin/server.mjs CHANGED
@@ -212,6 +212,7 @@ async function callPandastudio(command, args = {}) {
212
212
  } catch {
213
213
  body = { raw: text };
214
214
  }
215
+ if (res.status === 409 && body?.details?.code === "revision_conflict") return body;
215
216
  if (res.status >= 400) {
216
217
  throw new Error(`HTTP ${res.status}: ${body?.error ?? text.slice(0, 200)}`);
217
218
  }
@@ -635,6 +636,84 @@ const MOTION_ELEMENT_PROPS = {
635
636
  anchor: { type: "string", enum: ["auto", "free"] },
636
637
  };
637
638
  const TOOLS = [
639
+ {
640
+ name: "folder_list",
641
+ description:
642
+ "Manage flat folders in the active workspace. Delete keeps projects unless deleteProjects is true.",
643
+ inputSchema: {
644
+ type: "object",
645
+ properties: {},
646
+ required: [],
647
+ },
648
+ command: "folder.list",
649
+ },
650
+ {
651
+ name: "folder_create",
652
+ description:
653
+ "Manage flat folders in the active workspace. Delete keeps projects unless deleteProjects is true.",
654
+ inputSchema: {
655
+ type: "object",
656
+ properties: {
657
+ name: {
658
+ type: "string",
659
+ },
660
+ },
661
+ required: ["name"],
662
+ },
663
+ command: "folder.create",
664
+ },
665
+ {
666
+ name: "folder_rename",
667
+ description:
668
+ "Manage flat folders in the active workspace. Delete keeps projects unless deleteProjects is true.",
669
+ inputSchema: {
670
+ type: "object",
671
+ properties: {
672
+ id: {
673
+ type: "string",
674
+ },
675
+ name: {
676
+ type: "string",
677
+ },
678
+ },
679
+ required: ["id", "name"],
680
+ },
681
+ command: "folder.rename",
682
+ },
683
+ {
684
+ name: "folder_delete",
685
+ description:
686
+ "Manage flat folders in the active workspace. Delete keeps projects unless deleteProjects is true.",
687
+ inputSchema: {
688
+ type: "object",
689
+ properties: {
690
+ id: {
691
+ type: "string",
692
+ },
693
+ deleteProjects: {
694
+ type: "boolean",
695
+ default: false,
696
+ },
697
+ },
698
+ required: ["id"],
699
+ },
700
+ command: "folder.delete",
701
+ },
702
+ {
703
+ name: "folder_contents",
704
+ description:
705
+ "Manage flat folders in the active workspace. Delete keeps projects unless deleteProjects is true.",
706
+ inputSchema: {
707
+ type: "object",
708
+ properties: {
709
+ id: {
710
+ type: "string",
711
+ },
712
+ },
713
+ required: ["id"],
714
+ },
715
+ command: "folder.contents",
716
+ },
638
717
  // ── system + discovery ──────────────────────────────────────────
639
718
  {
640
719
  name: "system_status",
@@ -1272,6 +1351,8 @@ const TOOLS = [
1272
1351
  inputSchema: {
1273
1352
  type: "object",
1274
1353
  properties: {
1354
+ folderId: { type: ["string", "null"], description: "Folder UUID" },
1355
+ topLevel: { type: "boolean" },
1275
1356
  allWorkspaces: { type: "boolean" },
1276
1357
  sortBy: { type: "string", enum: ["modifiedAt", "createdAt", "name"] },
1277
1358
  order: { type: "string", enum: ["desc", "asc"] },
@@ -1287,6 +1368,7 @@ const TOOLS = [
1287
1368
  inputSchema: {
1288
1369
  type: "object",
1289
1370
  properties: {
1371
+ folderId: { type: ["string", "null"], description: "Folder UUID" },
1290
1372
  id: { type: "string" },
1291
1373
  path: { type: "string" },
1292
1374
  folder: { type: "string" },
@@ -1314,6 +1396,7 @@ const TOOLS = [
1314
1396
  inputSchema: {
1315
1397
  type: "object",
1316
1398
  properties: {
1399
+ folderId: { type: ["string", "null"], description: "Folder UUID" },
1317
1400
  id: { type: "string" },
1318
1401
  path: { type: "string" },
1319
1402
  },
@@ -1376,6 +1459,9 @@ const TOOLS = [
1376
1459
  inputSchema: {
1377
1460
  type: "object",
1378
1461
  properties: {
1462
+ folderId: { type: ["string", "null"], description: "Folder UUID" },
1463
+ parentProjectId: { type: "string" },
1464
+ originKind: { type: "string", enum: ["agent", "recipe"] },
1379
1465
  name: { type: "string" },
1380
1466
  withMedia: { type: "array", items: { type: "string" } },
1381
1467
  },
@@ -1390,6 +1476,7 @@ const TOOLS = [
1390
1476
  inputSchema: {
1391
1477
  type: "object",
1392
1478
  properties: {
1479
+ folderId: { type: ["string", "null"], description: "Folder UUID" },
1393
1480
  exportId: { type: "string" },
1394
1481
  shotId: { type: "string" },
1395
1482
  aspectRatio: { type: "string", description: "Default 9:16." },
@@ -1479,7 +1566,8 @@ const TOOLS = [
1479
1566
  },
1480
1567
  {
1481
1568
  name: "podcast_open_host",
1482
- description: "Open or focus the live podcast host window. Call podcast_status for the session code and guest link once it loads.",
1569
+ description:
1570
+ "Open or focus the live podcast host window. Call podcast_status for the session code and guest link once it loads.",
1483
1571
  inputSchema: {
1484
1572
  type: "object",
1485
1573
  properties: {
@@ -1490,10 +1578,22 @@ const TOOLS = [
1490
1578
  command: "podcast.open-host",
1491
1579
  },
1492
1580
  ...[
1493
- ["status", "Read the live podcast session code, guest link, participants, recording, and sharing state."],
1494
- ["start-recording", "Start recording the host and signal connected guests to record. Returns once recording has really started; fails with the reason otherwise."],
1495
- ["stop-recording", "Stop recording and finalize podcast media. Check podcast_status for finalization and guest uploads."],
1496
- ["start-screen-share", "Open the desktop picker to share and record the host screen during a podcast. Waits for the host to choose; fails if the picker is cancelled or capture fails."],
1581
+ [
1582
+ "status",
1583
+ "Read the live podcast session code, guest link, participants, recording, and sharing state.",
1584
+ ],
1585
+ [
1586
+ "start-recording",
1587
+ "Start recording the host and signal connected guests to record. Returns once recording has really started; fails with the reason otherwise.",
1588
+ ],
1589
+ [
1590
+ "stop-recording",
1591
+ "Stop recording and finalize podcast media. Check podcast_status for finalization and guest uploads.",
1592
+ ],
1593
+ [
1594
+ "start-screen-share",
1595
+ "Open the desktop picker to share and record the host screen during a podcast. Waits for the host to choose; fails if the picker is cancelled or capture fails.",
1596
+ ],
1497
1597
  ["stop-screen-share", "Stop and finalize the host's podcast screen share."],
1498
1598
  ["open-in-editor", "Open the finished podcast in the editor when guest media has arrived."],
1499
1599
  ].map(([noun, description]) => ({
@@ -1573,7 +1673,7 @@ const TOOLS = [
1573
1673
  {
1574
1674
  name: "project_update_motion_graphic",
1575
1675
  description:
1576
- "Edit a generated graphic (overlay with generatedFrom) and re-render it in place, keeping timing and position. ASYNC: { jobId }.",
1676
+ "Edit a generated graphic (overlay with generatedFrom) and re-render it in place, keeping timing and position. ASYNC: { jobId }. Concurrent saves are merged and retried (three attempts). Exhausted conflicts report code revision_conflict / status 409 (job.errorDetails for async jobs).",
1577
1677
  inputSchema: {
1578
1678
  type: "object",
1579
1679
  properties: {
@@ -1597,7 +1697,7 @@ const TOOLS = [
1597
1697
  {
1598
1698
  name: "project_add_emoji",
1599
1699
  description:
1600
- "Place an animated emoji overlay. ASYNC: { jobId }; job_wait gives result.overlayId.",
1700
+ "Place an animated emoji overlay. ASYNC: { jobId }; job_wait gives result.overlayId. Concurrent saves are merged and retried (three attempts). Exhausted conflicts report code revision_conflict / status 409 (job.errorDetails for async jobs).",
1601
1701
  inputSchema: {
1602
1702
  type: "object",
1603
1703
  properties: {
@@ -1803,7 +1903,7 @@ const TOOLS = [
1803
1903
  {
1804
1904
  name: "project_add_lower_third",
1805
1905
  description:
1806
- "Render a nameplate and place it at atMs in one call, laid out for the project's aspect. ASYNC: { jobId }; job_wait gives result.overlayId once placed.",
1906
+ "Render a nameplate and place it at atMs in one call, laid out for the project's aspect. ASYNC: { jobId }; job_wait gives result.overlayId once placed. Concurrent saves are merged and retried (three attempts). Exhausted conflicts report code revision_conflict / status 409 (job.errorDetails for async jobs).",
1807
1907
  inputSchema: {
1808
1908
  type: "object",
1809
1909
  properties: {
@@ -1840,7 +1940,7 @@ const TOOLS = [
1840
1940
  {
1841
1941
  name: "project_add_title_behind",
1842
1942
  description:
1843
- "1-3 huge words at head height BEHIND the presenter. ASYNC: job_wait gives regionId, face.",
1943
+ "1-3 huge words at head height BEHIND the presenter. ASYNC: job_wait gives regionId, face. Concurrent saves are merged and retried (three attempts). Exhausted conflicts report code revision_conflict / status 409 (job.errorDetails for async jobs).",
1844
1944
  inputSchema: {
1845
1945
  type: "object",
1846
1946
  properties: {
@@ -2587,7 +2687,7 @@ const TOOLS = [
2587
2687
  {
2588
2688
  name: "audio_probe",
2589
2689
  description:
2590
- "Per-clip audio stats: hasAudio, mean/max dB, silence spans (source time), plus audio overlays. A few seconds per clip.",
2690
+ "Per-clip audio stats: hasAudio, integratedLufs (null if unavailable/silent), tooQuiet (< -30 LUFS), mean/max dB, silence spans (source time), plus audio overlays. A few seconds per clip.",
2591
2691
  inputSchema: {
2592
2692
  type: "object",
2593
2693
  properties: {
@@ -2987,7 +3087,7 @@ const TOOLS = [
2987
3087
  {
2988
3088
  name: "project_center_camera_on_face",
2989
3089
  description:
2990
- "Keep the presenter's face centred in the camera card by tracking it (can take a minute). clear: true turns it off.",
3090
+ "Keep the presenter's face centred in the camera card by tracking it (can take a minute). clear: true turns it off. Concurrent saves are merged and retried (three attempts). Exhausted conflicts report code revision_conflict / status 409 (job.errorDetails for async jobs).",
2991
3091
  inputSchema: {
2992
3092
  type: "object",
2993
3093
  properties: {
@@ -3004,7 +3104,7 @@ const TOOLS = [
3004
3104
  {
3005
3105
  name: "project_batch",
3006
3106
  description:
3007
- "Run up to 200 verb.noun commands on one project in order (id / path filled in; not atomic; stops at the first failure by default). Returns { ran, failed, results[] }.",
3107
+ "Run up to 200 verb.noun commands on one project in order (id / path filled in; not atomic; stops at the first failure by default). Returns { ran, failed, results[] }. Concurrent saves are merged and retried (three attempts). Exhausted conflicts report code revision_conflict / status 409 (job.errorDetails for async jobs).",
3008
3108
  inputSchema: {
3009
3109
  type: "object",
3010
3110
  properties: {
@@ -3225,7 +3325,7 @@ const TOOLS = [
3225
3325
  {
3226
3326
  name: "project_auto_reframe",
3227
3327
  description:
3228
- "Active-speaker auto-reframe of landscape multi-person footage for vertical / square: frames whoever talks, shot by shot. split=true stacks two people (podcast / interview wide) one above the other on a 9:16 canvas. Slow (minutes). clear reverts.",
3328
+ "Active-speaker auto-reframe of landscape multi-person footage for vertical / square: frames whoever talks, shot by shot. split=true stacks two people (podcast / interview wide) one above the other on a 9:16 canvas. Slow (minutes). clear reverts. Concurrent saves are merged and retried (three attempts). Exhausted conflicts report code revision_conflict / status 409 (job.errorDetails for async jobs).",
3229
3329
  inputSchema: {
3230
3330
  type: "object",
3231
3331
  properties: {
@@ -3686,16 +3786,52 @@ const TOOLS = [
3686
3786
  },
3687
3787
 
3688
3788
  // ── transcript-based editing ────────────────────────────────────
3789
+ {
3790
+ name: "transcript_detect_language",
3791
+ description:
3792
+ "Guess a clip's language locally from up to 30 seconds. Returns guess or null when unavailable. Confirm with the user before cloud transcription.",
3793
+ inputSchema: {
3794
+ type: "object",
3795
+ properties: { id: { type: "string" }, path: { type: "string" }, clipId: { type: "string" } },
3796
+ },
3797
+ command: "transcript.detect-language",
3798
+ },
3799
+
3800
+ {
3801
+ name: "system_set_transcription_preferences",
3802
+ description:
3803
+ "Save workspace language and provider atomically. Confirm consent to upload audio for cloud. Cloud cannot use auto.",
3804
+ inputSchema: {
3805
+ type: "object",
3806
+ required: ["language", "provider"],
3807
+ properties: {
3808
+ language: { type: "string" },
3809
+ provider: { type: "string", enum: ["local", "deepgram", "elevenlabs"] },
3810
+ },
3811
+ },
3812
+ command: "system.set-transcription-preferences",
3813
+ },
3814
+
3689
3815
  {
3690
3816
  name: "transcript_transcribe",
3691
3817
  description:
3692
- "Transcribe clips with word timestamps (default: only clips without words). To REDO a transcript (wrong language, bad run) fix the language first, then force: true (all clips) or clipId. Word fixes are re-applied; tell the user about result droppedWordEdits[] (fixes that may need redoing). Each clip is transcribed only over the part it plays (head trim to out-point, e.g. one half of a split; result transcribedRanges[]). ASYNC: { jobId }, job_wait.",
3818
+ "Transcribe clips with word timestamps (default: only clips without words). To REDO a transcript (wrong language, bad run) confirm language, pass per-run language and provider overrides, then force: true (all clips) or clipId. Word fixes are re-applied; tell the user about result droppedWordEdits[] (fixes that may need redoing). Each clip is transcribed only over the part it plays (head trim to out-point, e.g. one half of a split; result transcribedRanges[]). ASYNC: { jobId }, job_wait. Concurrent saves are merged and retried (three attempts). Exhausted conflicts report code revision_conflict / status 409 (job.errorDetails for async jobs).",
3693
3819
  inputSchema: {
3694
3820
  type: "object",
3695
3821
  properties: {
3696
3822
  id: { type: "string" },
3697
3823
  path: { type: "string" },
3698
3824
  clipId: { type: "string", description: "Only this clip, even if already transcribed." },
3825
+ language: {
3826
+ type: "string",
3827
+ description:
3828
+ "Per-run spoken language name (english, tamil, etc.). Cloud cannot use auto.",
3829
+ },
3830
+ provider: {
3831
+ type: "string",
3832
+ enum: ["local", "deepgram", "elevenlabs"],
3833
+ description: "Per-run provider. Audio is sent to the selected cloud provider.",
3834
+ },
3699
3835
  force: { type: "boolean", description: "Re-transcribe clips that already have words." },
3700
3836
  },
3701
3837
  },
@@ -3854,7 +3990,7 @@ const TOOLS = [
3854
3990
  {
3855
3991
  name: "audio_clean",
3856
3992
  description:
3857
- "Denoise each clip's audio, optionally reducing room echo (original kept). ASYNC: { jobId }, job_wait.",
3993
+ "Denoise each clip’s audio, optionally reducing room echo. Speech below -30 LUFS is boosted to -16 LUFS / -1.5 dBTP by default; warnings explain boosts. Original kept; audio_reset_clean restores it. ASYNC: { jobId }, job_wait. Concurrent saves are merged and retried (three attempts); exhausted conflicts report code revision_conflict / status 409.",
3858
3994
  inputSchema: {
3859
3995
  type: "object",
3860
3996
  properties: {
@@ -3862,11 +3998,42 @@ const TOOLS = [
3862
3998
  path: { type: "string" },
3863
3999
  clipId: { type: "string" },
3864
4000
  echo: { type: "boolean", description: "Only when the user mentions echo / reverb." },
4001
+ normalize: {
4002
+ type: "boolean",
4003
+ description: "Default true: boost very quiet speech. Set false to opt out.",
4004
+ },
3865
4005
  },
3866
4006
  },
3867
4007
  command: "audio.clean",
3868
4008
  },
3869
4009
 
4010
+ {
4011
+ name: "audio_normalize",
4012
+ description:
4013
+ "Normalize a clip’s active audio without denoising. Default -16 LUFS, -1.5 dBTP ceiling. Silent audio skipped with warnings. Original kept. ASYNC: job_wait.",
4014
+ inputSchema: {
4015
+ type: "object",
4016
+ properties: {
4017
+ id: { type: "string" },
4018
+ path: { type: "string" },
4019
+ clipId: { type: "string" },
4020
+ targetLufs: { type: "number", minimum: -70, maximum: -5, default: -16 },
4021
+ },
4022
+ required: ["clipId"],
4023
+ },
4024
+ command: "audio.normalize",
4025
+ },
4026
+ {
4027
+ name: "audio_reset_clean",
4028
+ description:
4029
+ "Restore original clip audio by clearing cleaned/normalized audio and echo metadata. Optional clipId; otherwise all clips. Keeps generated files. Synchronous.",
4030
+ inputSchema: {
4031
+ type: "object",
4032
+ properties: { id: { type: "string" }, path: { type: "string" }, clipId: { type: "string" } },
4033
+ },
4034
+ command: "audio.reset-clean",
4035
+ },
4036
+
3870
4037
  // ── captions ────────────────────────────────────────────────────
3871
4038
  {
3872
4039
  name: "caption_toggle",
@@ -4573,7 +4740,7 @@ const TOOLS = [
4573
4740
  {
4574
4741
  name: "media_download_url",
4575
4742
  description:
4576
- "Download a video or its audio from a page link (YouTube, Vimeo...). Only content the user has rights to. ASYNC: job_wait gives { path, title, durationMs }.",
4743
+ "Download a video or its audio from a page link (YouTube, Vimeo...). Only content the user has rights to. ASYNC: job_wait gives { path, title, durationMs }. Project placement retries concurrent-save conflicts (three attempts); exhaustion is job.errorDetails with revision_conflict / status 409.",
4577
4744
  inputSchema: {
4578
4745
  type: "object",
4579
4746
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@writepanda/mcp",
3
- "version": "1.210.0",
3
+ "version": "1.213.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",