@writepanda/mcp 1.207.0 → 1.211.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 +195 -29
  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
  }
@@ -627,7 +628,11 @@ const MOTION_ELEMENT_PROPS = {
627
628
  enum: ["auto", "none", "pop", "whoosh", "hit", "tick", "bell"],
628
629
  description: "Role for project_compose_soundtrack.",
629
630
  },
630
- zIndex: { type: "number" },
631
+ zIndex: {
632
+ type: "number",
633
+ description:
634
+ "Stack order, higher on top (default 0). Front elements share one stack with media overlays and annotations at group base + zIndex (B-roll 0, overlays 1000, annotations 2000, front elements 3000), so by default they draw over both; -2500 drops one under the default overlays.",
635
+ },
631
636
  anchor: { type: "string", enum: ["auto", "free"] },
632
637
  };
633
638
  const TOOLS = [
@@ -635,7 +640,7 @@ const TOOLS = [
635
640
  {
636
641
  name: "system_status",
637
642
  description:
638
- "App version and license; automationGated true = trial expired, or the Solo plan (license.agentEditing.allowed false: no agent editing, relay license.agentEditing.message to the user), and every other tool fails; license.socialPublishing.allowed false = the plan has no Instagram/TikTok/social publishing (Creator, Team, Pro only). Call first.",
643
+ "App version and license; automationGated true = trial expired, or the Solo plan (license.agentEditing.allowed false: no agent editing, relay license.agentEditing.message to the user), and every other tool fails; license.socialPublishing.allowed false = the plan has no Instagram/TikTok/social publishing (Creator, Team, Pro only); license.exportQuality.allowed = the export_start qualities that work (free trial: draft 720p only; standard/high/ultra need a license). Call first.",
639
644
  inputSchema: { type: "object", properties: {} },
640
645
  command: "system.status",
641
646
  },
@@ -725,7 +730,7 @@ const TOOLS = [
725
730
  {
726
731
  name: "system_get_transcription_language",
727
732
  description:
728
- "Workspace transcription language (auto = Parakeet, English + 25 European languages).",
733
+ "Workspace transcription language. auto = Parakeet TDT v3, which detects the language itself across 25 languages, English included: Bulgarian, Croatian, Czech, Danish, Dutch, English, Estonian, Finnish, French, German, Greek, Hungarian, Italian, Latvian, Lithuanian, Maltese, Polish, Portuguese, Romanian, Russian, Slovak, Slovenian, Spanish, Swedish, Ukrainian. Anything else names a Whisper language.",
729
734
  inputSchema: { type: "object", properties: {} },
730
735
  command: "system.get-transcription-language",
731
736
  },
@@ -752,7 +757,7 @@ const TOOLS = [
752
757
  {
753
758
  name: "system_set_transcription_language",
754
759
  description:
755
- "Set the workspace transcription language. English = auto (english/en are saved as auto). Non-auto uses Whisper, a ~1.1 GB download: tell the user first (system.is-whisper-model-downloaded via pandastudio_call).",
760
+ "Set the workspace transcription language. auto = Parakeet TDT v3, which detects the language itself across 25 languages, English included: Bulgarian, Croatian, Czech, Danish, Dutch, English, Estonian, Finnish, French, German, Greek, Hungarian, Italian, Latvian, Lithuanian, Maltese, Polish, Portuguese, Romanian, Russian, Slovak, Slovenian, Spanish, Swedish, Ukrainian. For any of those (e.g. Romanian) use auto; their names are accepted and saved as auto. The other values use Whisper, a ~1.1 GB download: tell the user first (system.is-whisper-model-downloaded via pandastudio_call).",
756
761
  inputSchema: {
757
762
  type: "object",
758
763
  properties: {
@@ -762,6 +767,30 @@ const TOOLS = [
762
767
  "auto",
763
768
  "english",
764
769
  "en",
770
+ "bulgarian",
771
+ "croatian",
772
+ "czech",
773
+ "danish",
774
+ "dutch",
775
+ "estonian",
776
+ "finnish",
777
+ "french",
778
+ "german",
779
+ "greek",
780
+ "hungarian",
781
+ "italian",
782
+ "latvian",
783
+ "lithuanian",
784
+ "maltese",
785
+ "polish",
786
+ "portuguese",
787
+ "romanian",
788
+ "russian",
789
+ "slovak",
790
+ "slovenian",
791
+ "spanish",
792
+ "swedish",
793
+ "ukrainian",
765
794
  "chinese",
766
795
  "japanese",
767
796
  "korean",
@@ -1048,8 +1077,14 @@ const TOOLS = [
1048
1077
  },
1049
1078
  {
1050
1079
  name: "youtube_connect",
1051
- description: "Connect a YouTube account via browser sign-in (only when the user asks).",
1052
- inputSchema: { type: "object", properties: {} },
1080
+ description:
1081
+ "Connect a YouTube account via browser sign-in (only when the user asks). Pass email to reconnect an existing account. permissionIssue set = the user unticked a Google permission: relay its message.",
1082
+ inputSchema: {
1083
+ type: "object",
1084
+ properties: {
1085
+ email: { type: "string", description: "Reconnect: that account's email." },
1086
+ },
1087
+ },
1053
1088
  command: "youtube.connect",
1054
1089
  },
1055
1090
  {
@@ -1065,13 +1100,15 @@ const TOOLS = [
1065
1100
  },
1066
1101
  {
1067
1102
  name: "youtube_list_accounts",
1068
- description: "Connected YouTube accounts and channels (pick before export_publish_youtube).",
1103
+ description:
1104
+ "Connected YouTube accounts and channels (pick before export_publish_youtube). issues[] = accounts missing a Google permission (code youtube_permission_missing): relay message, follow howToFix (the user reconnects in Settings > YouTube, leaving every box ticked; you can't do the sign-in).",
1069
1105
  inputSchema: { type: "object", properties: {} },
1070
1106
  command: "youtube.list-accounts",
1071
1107
  },
1072
1108
  {
1073
1109
  name: "youtube_list_channels",
1074
- description: "Refresh an account's channel list from YouTube (bypasses cache).",
1110
+ description:
1111
+ "Refresh an account's channel list from YouTube (bypasses cache). Error code youtube_permission_missing = channel permission unticked on Google's screen: relay message, follow howToFix; canStillPublish true = publish without channelId.",
1075
1112
  inputSchema: {
1076
1113
  type: "object",
1077
1114
  properties: { accountId: { type: "string" } },
@@ -1082,7 +1119,7 @@ const TOOLS = [
1082
1119
  {
1083
1120
  name: "export_publish_youtube",
1084
1121
  description:
1085
- "Publish an export entry to YouTube. Slow. Check the entry's youtubeVideoId before republishing. Returns { videoUrl }.",
1122
+ "Publish an export entry to YouTube. Slow. Check the entry's youtubeVideoId before republishing. channelId optional (uploads go to the account's own channel; omit when channels can't be listed). Error code youtube_permission_missing: relay message, follow howToFix. Returns { videoUrl, channelId }.",
1086
1123
  inputSchema: {
1087
1124
  type: "object",
1088
1125
  properties: {
@@ -1096,7 +1133,7 @@ const TOOLS = [
1096
1133
  privacyStatus: { type: "string", enum: ["public", "unlisted", "private"] },
1097
1134
  setThumbnail: { type: "boolean" },
1098
1135
  },
1099
- required: ["id", "accountId", "channelId", "title"],
1136
+ required: ["id", "accountId", "title"],
1100
1137
  },
1101
1138
  command: "export.publish-youtube",
1102
1139
  },
@@ -1380,7 +1417,7 @@ const TOOLS = [
1380
1417
  {
1381
1418
  name: "project_add_clip",
1382
1419
  description:
1383
- "Append or insert a video clip, or a still image shown for durationMs, on the main track (later content moves with it). Mid-recording: project_split_clip, then atIndex = rightClipIndex.",
1420
+ "Append or insert a video clip, or a still image shown for durationMs, on the main track (later content moves with it). atMs (edited time) inserts at that moment, splitting the clip under it; add replaceToMs to replace [atMs, replaceToMs) with the clip (e.g. a re-recorded take).",
1384
1421
  inputSchema: {
1385
1422
  type: "object",
1386
1423
  properties: {
@@ -1391,6 +1428,14 @@ const TOOLS = [
1391
1428
  fill: { type: "boolean", description: "Images: cover the frame (default true)." },
1392
1429
  webcam: { type: "string", description: "Guest source: a two-speaker podcast clip." },
1393
1430
  atIndex: { type: "number", description: "0-based; omit = append." },
1431
+ atMs: {
1432
+ type: "number",
1433
+ description: "Insert at this edited ms (splits the clip there; not with atIndex).",
1434
+ },
1435
+ replaceToMs: {
1436
+ type: "number",
1437
+ description: "With atMs: cut [atMs, replaceToMs) (a trim) and put the clip there.",
1438
+ },
1394
1439
  expectedRevision: { type: "number" },
1395
1440
  },
1396
1441
  required: ["media"],
@@ -1433,6 +1478,79 @@ const TOOLS = [
1433
1478
  },
1434
1479
  command: "project.add-podcast-clip",
1435
1480
  },
1481
+ {
1482
+ name: "podcast_open_host",
1483
+ description:
1484
+ "Open or focus the live podcast host window. Call podcast_status for the session code and guest link once it loads.",
1485
+ inputSchema: {
1486
+ type: "object",
1487
+ properties: {
1488
+ sessionCode: { type: "string", description: "Resume an existing session." },
1489
+ projectPath: { type: "string", description: "Matching podcast project when resuming." },
1490
+ },
1491
+ },
1492
+ command: "podcast.open-host",
1493
+ },
1494
+ ...[
1495
+ [
1496
+ "status",
1497
+ "Read the live podcast session code, guest link, participants, recording, and sharing state.",
1498
+ ],
1499
+ [
1500
+ "start-recording",
1501
+ "Start recording the host and signal connected guests to record. Returns once recording has really started; fails with the reason otherwise.",
1502
+ ],
1503
+ [
1504
+ "stop-recording",
1505
+ "Stop recording and finalize podcast media. Check podcast_status for finalization and guest uploads.",
1506
+ ],
1507
+ [
1508
+ "start-screen-share",
1509
+ "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.",
1510
+ ],
1511
+ ["stop-screen-share", "Stop and finalize the host's podcast screen share."],
1512
+ ["open-in-editor", "Open the finished podcast in the editor when guest media has arrived."],
1513
+ ].map(([noun, description]) => ({
1514
+ name: `podcast_${noun.replaceAll("-", "_")}`,
1515
+ description,
1516
+ inputSchema: { type: "object", properties: {} },
1517
+ command: `podcast.${noun}`,
1518
+ })),
1519
+ {
1520
+ name: "project_set_podcast_source_crop",
1521
+ description:
1522
+ "Frame one podcast camera or screen-share source independently. Use source=host|guest|guest-2|guest-3|share:0; normalized x,y,width,height. 0,0,1,1 resets the crop. Preview and export use the same framing.",
1523
+ inputSchema: {
1524
+ type: "object",
1525
+ properties: {
1526
+ id: { type: "string" },
1527
+ path: { type: "string" },
1528
+ clipId: { type: "string" },
1529
+ source: { type: "string", description: "host, guest, guest-2, guest-3, or share:<index>." },
1530
+ x: { type: "number", description: "Left edge, 0-1." },
1531
+ y: { type: "number", description: "Top edge, 0-1." },
1532
+ width: { type: "number", description: "Crop width, >0 to 1." },
1533
+ height: { type: "number", description: "Crop height, >0 to 1." },
1534
+ expectedRevision: { type: "number" },
1535
+ },
1536
+ required: ["clipId", "source", "x", "y", "width", "height"],
1537
+ },
1538
+ command: "project.set-podcast-source-crop",
1539
+ },
1540
+ {
1541
+ name: "project_list_podcast_screen_shares",
1542
+ description:
1543
+ "List the screen-share intervals shown on the podcast editor timeline. Returns speaker, clip-local and merged-source timing, edited timing after trims/speed, and whether each interval is visible. Read-only.",
1544
+ inputSchema: {
1545
+ type: "object",
1546
+ properties: {
1547
+ id: { type: "string" },
1548
+ path: { type: "string" },
1549
+ clipId: { type: "string", description: "Optional podcast clip filter." },
1550
+ },
1551
+ },
1552
+ command: "project.list-podcast-screen-shares",
1553
+ },
1436
1554
  {
1437
1555
  name: "project_set_participant_offset",
1438
1556
  description: "Nudge one guest's A/V sync on a podcast composite (host is the reference).",
@@ -1469,7 +1587,7 @@ const TOOLS = [
1469
1587
  {
1470
1588
  name: "project_update_motion_graphic",
1471
1589
  description:
1472
- "Edit a generated graphic (overlay with generatedFrom) and re-render it in place, keeping timing and position. ASYNC: { jobId }.",
1590
+ "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).",
1473
1591
  inputSchema: {
1474
1592
  type: "object",
1475
1593
  properties: {
@@ -1493,7 +1611,7 @@ const TOOLS = [
1493
1611
  {
1494
1612
  name: "project_add_emoji",
1495
1613
  description:
1496
- "Place an animated emoji overlay. ASYNC: { jobId }; job_wait gives result.overlayId.",
1614
+ "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).",
1497
1615
  inputSchema: {
1498
1616
  type: "object",
1499
1617
  properties: {
@@ -1699,7 +1817,7 @@ const TOOLS = [
1699
1817
  {
1700
1818
  name: "project_add_lower_third",
1701
1819
  description:
1702
- "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.",
1820
+ "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).",
1703
1821
  inputSchema: {
1704
1822
  type: "object",
1705
1823
  properties: {
@@ -1736,7 +1854,7 @@ const TOOLS = [
1736
1854
  {
1737
1855
  name: "project_add_title_behind",
1738
1856
  description:
1739
- "1-3 huge words at head height BEHIND the presenter. ASYNC: job_wait gives regionId, face.",
1857
+ "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).",
1740
1858
  inputSchema: {
1741
1859
  type: "object",
1742
1860
  properties: {
@@ -2483,7 +2601,7 @@ const TOOLS = [
2483
2601
  {
2484
2602
  name: "audio_probe",
2485
2603
  description:
2486
- "Per-clip audio stats: hasAudio, mean/max dB, silence spans (source time), plus audio overlays. A few seconds per clip.",
2604
+ "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.",
2487
2605
  inputSchema: {
2488
2606
  type: "object",
2489
2607
  properties: {
@@ -2883,7 +3001,7 @@ const TOOLS = [
2883
3001
  {
2884
3002
  name: "project_center_camera_on_face",
2885
3003
  description:
2886
- "Keep the presenter's face centred in the camera card by tracking it (can take a minute). clear: true turns it off.",
3004
+ "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).",
2887
3005
  inputSchema: {
2888
3006
  type: "object",
2889
3007
  properties: {
@@ -2900,7 +3018,7 @@ const TOOLS = [
2900
3018
  {
2901
3019
  name: "project_batch",
2902
3020
  description:
2903
- "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[] }.",
3021
+ "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).",
2904
3022
  inputSchema: {
2905
3023
  type: "object",
2906
3024
  properties: {
@@ -2923,7 +3041,7 @@ const TOOLS = [
2923
3041
  {
2924
3042
  name: "project_update_region",
2925
3043
  description:
2926
- "Patch a region; unspecified fields are kept. Patchable: zoom (depth, focusX, focusY, focusMode static|auto), trim / speed (startMs, endMs, speed, rampIn, rampOut, preservePitch, freezeMs, reverse, reverseAudio), fx (atMs, durationMs, opacity, blendMode, speed), annotation, overlay (x, y, width, height in canvas %, layer, opacity, blendMode, muted, volume, loop, motionBlur), adjustment (startMs, endMs, blendMode), motion (motionBlur), clip-transform (startMs, endMs, preset, transitionMs, easing, bezier), background-effect (as project_add_background_effect), audio-overlay (startMs, endMs, sourceStartMs, volume 0-2, fadeIn, fadeOut).",
3044
+ "Patch a region; unspecified fields are kept. Patchable: zoom (depth, focusX, focusY, focusMode static|auto), trim / speed (startMs, endMs, speed, rampIn, rampOut, preservePitch, freezeMs, reverse, reverseAudio), fx (atMs, durationMs, opacity, blendMode, speed, zIndex), annotation (zIndex), overlay (x, y, width, height in canvas %, layer, opacity, blendMode, muted, volume, loop, motionBlur, zIndex), adjustment (startMs, endMs, blendMode), motion (motionBlur), clip-transform (startMs, endMs, preset, transitionMs, easing, bezier), background-effect (as project_add_background_effect), audio-overlay (startMs, endMs, sourceStartMs, volume 0-2, fadeIn, fadeOut).",
2927
3045
  inputSchema: {
2928
3046
  type: "object",
2929
3047
  properties: {
@@ -2932,7 +3050,11 @@ const TOOLS = [
2932
3050
  path: { type: "string" },
2933
3051
  regionType: { type: "string", enum: REGION_TYPES },
2934
3052
  regionId: { type: "string" },
2935
- patch: { type: "object", description: "e.g. { depth: 4 }; null clears a value." },
3053
+ patch: {
3054
+ type: "object",
3055
+ description:
3056
+ "e.g. { depth: 4 }; null clears a value. zIndex (overlay / annotation / fx): stack order, higher on top; overlays, annotations and front motion elements share one stack at group base + zIndex (B-roll 0, overlays 1000, annotations 2000, motion elements 3000).",
3057
+ },
2936
3058
  expectedRevision: { type: "number" },
2937
3059
  },
2938
3060
  required: ["regionType", "regionId", "patch"],
@@ -3117,7 +3239,7 @@ const TOOLS = [
3117
3239
  {
3118
3240
  name: "project_auto_reframe",
3119
3241
  description:
3120
- "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.",
3242
+ "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).",
3121
3243
  inputSchema: {
3122
3244
  type: "object",
3123
3245
  properties: {
@@ -3141,13 +3263,19 @@ const TOOLS = [
3141
3263
  },
3142
3264
  {
3143
3265
  name: "project_set_export_settings",
3144
- description: "Project export defaults (Export dialog and export_start).",
3266
+ description:
3267
+ "Project export defaults (Export dialog and export_start). quality good and source need a license: a free trial gets quality_requires_license, use medium.",
3145
3268
  inputSchema: {
3146
3269
  type: "object",
3147
3270
  properties: {
3148
3271
  id: { type: "string" },
3149
3272
  path: { type: "string" },
3150
- quality: { type: "string", enum: ["medium", "good", "source"] },
3273
+ quality: {
3274
+ type: "string",
3275
+ enum: ["medium", "good", "source"],
3276
+ description:
3277
+ "medium 720p, good 1080p, source = source resolution (good/source license only).",
3278
+ },
3151
3279
  format: { type: "string", enum: ["mp4"] },
3152
3280
  normalizeLoudness: {
3153
3281
  type: ["boolean", "string"],
@@ -3575,7 +3703,7 @@ const TOOLS = [
3575
3703
  {
3576
3704
  name: "transcript_transcribe",
3577
3705
  description:
3578
- "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). ASYNC: { jobId }, job_wait.",
3706
+ "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. Concurrent saves are merged and retried (three attempts). Exhausted conflicts report code revision_conflict / status 409 (job.errorDetails for async jobs).",
3579
3707
  inputSchema: {
3580
3708
  type: "object",
3581
3709
  properties: {
@@ -3740,7 +3868,7 @@ const TOOLS = [
3740
3868
  {
3741
3869
  name: "audio_clean",
3742
3870
  description:
3743
- "Denoise each clip's audio, optionally reducing room echo (original kept). ASYNC: { jobId }, job_wait.",
3871
+ "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.",
3744
3872
  inputSchema: {
3745
3873
  type: "object",
3746
3874
  properties: {
@@ -3748,11 +3876,42 @@ const TOOLS = [
3748
3876
  path: { type: "string" },
3749
3877
  clipId: { type: "string" },
3750
3878
  echo: { type: "boolean", description: "Only when the user mentions echo / reverb." },
3879
+ normalize: {
3880
+ type: "boolean",
3881
+ description: "Default true: boost very quiet speech. Set false to opt out.",
3882
+ },
3751
3883
  },
3752
3884
  },
3753
3885
  command: "audio.clean",
3754
3886
  },
3755
3887
 
3888
+ {
3889
+ name: "audio_normalize",
3890
+ description:
3891
+ "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.",
3892
+ inputSchema: {
3893
+ type: "object",
3894
+ properties: {
3895
+ id: { type: "string" },
3896
+ path: { type: "string" },
3897
+ clipId: { type: "string" },
3898
+ targetLufs: { type: "number", minimum: -70, maximum: -5, default: -16 },
3899
+ },
3900
+ required: ["clipId"],
3901
+ },
3902
+ command: "audio.normalize",
3903
+ },
3904
+ {
3905
+ name: "audio_reset_clean",
3906
+ description:
3907
+ "Restore original clip audio by clearing cleaned/normalized audio and echo metadata. Optional clipId; otherwise all clips. Keeps generated files. Synchronous.",
3908
+ inputSchema: {
3909
+ type: "object",
3910
+ properties: { id: { type: "string" }, path: { type: "string" }, clipId: { type: "string" } },
3911
+ },
3912
+ command: "audio.reset-clean",
3913
+ },
3914
+
3756
3915
  // ── captions ────────────────────────────────────────────────────
3757
3916
  {
3758
3917
  name: "caption_toggle",
@@ -4459,7 +4618,7 @@ const TOOLS = [
4459
4618
  {
4460
4619
  name: "media_download_url",
4461
4620
  description:
4462
- "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 }.",
4621
+ "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.",
4463
4622
  inputSchema: {
4464
4623
  type: "object",
4465
4624
  properties: {
@@ -5043,14 +5202,19 @@ const TOOLS = [
5043
5202
  {
5044
5203
  name: "export_start",
5045
5204
  description:
5046
- "Render the project to MP4. ASYNC: { jobId, outputPath }; job_wait. Tell the user any warning and the result's loudness before / after.",
5205
+ "Render the project to MP4. ASYNC: { jobId, outputPath, quality }; job_wait. Tell the user any warning and the result's loudness before / after. Free trial: draft (720p) only (system_status license.exportQuality); standard/high/ultra fail with quality_requires_license.",
5047
5206
  inputSchema: {
5048
5207
  type: "object",
5049
5208
  properties: {
5050
5209
  id: { type: "string" },
5051
5210
  path: { type: "string" },
5052
5211
  outputPath: { type: "string" },
5053
- quality: { type: "string", enum: ["draft", "standard", "high", "ultra"] },
5212
+ quality: {
5213
+ type: "string",
5214
+ enum: ["draft", "standard", "high", "ultra"],
5215
+ description:
5216
+ "draft 720p, standard 1080p, high/ultra source resolution (standard and up: license only). Omit = high, or draft on a trial.",
5217
+ },
5054
5218
  normalizeLoudness: {
5055
5219
  type: ["boolean", "string"],
5056
5220
  description: "streaming / podcast / off; omit = project setting.",
@@ -5165,7 +5329,7 @@ const TOOLS = [
5165
5329
  {
5166
5330
  name: "clips_export",
5167
5331
  description:
5168
- "Export a clip set's finished clips to MP4 (all, or clipIds) into Movies/PandaStudio Clips/<video>/. Async: returns { jobId, outputDir }; job_wait for { exported[] }.",
5332
+ "Export a clip set's finished clips to MP4 (all, or clipIds) into Movies/PandaStudio Clips/<video>/, at standard quality (1080p; 720p on a free trial). Async: returns { jobId, outputDir }; job_wait for { exported[] }.",
5169
5333
  inputSchema: {
5170
5334
  type: "object",
5171
5335
  properties: {
@@ -5483,6 +5647,8 @@ const INSTRUCTIONS = `PandaStudio is a desktop video editor; these tools drive t
5483
5647
 
5484
5648
  Start with system_status. Unknown arguments are rejected. Verbs without a tool of their own (system_list_commands) go through pandastudio_call.
5485
5649
 
5650
+ Read-only tools (project_read, transcript_get / _search / _find_issues, audio_probe, project_render_frame / _render_sheet, timeline_*, ...) use the project open in the editor when you pass no id or path; edits always need the id.
5651
+
5486
5652
  Before an editing pass, read the skill's "Which tool for which moment" section (skill_read section="Which tool for which moment").
5487
5653
 
5488
5654
  "Edit my video" / "make this a Short" with no specifics = this pipeline, in order. Confirm scope once ("full polish, or just part of it?"), then run it all:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@writepanda/mcp",
3
- "version": "1.207.0",
3
+ "version": "1.211.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",