@writepanda/mcp 1.285.0 → 1.286.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 +128 -17
  2. package/package.json +1 -1
package/bin/server.mjs CHANGED
@@ -1416,7 +1416,7 @@ const TOOLS = [
1416
1416
  {
1417
1417
  name: "youtube_list_accounts",
1418
1418
  description:
1419
- "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).",
1419
+ "Connected YouTube accounts and channels (pick before export_publish_youtube). Each channel's userLastPrivacy = what the user last picked in the app's Publish window (use only when they ask for their usual setting). 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).",
1420
1420
  inputSchema: { type: "object", properties: {} },
1421
1421
  command: "youtube.list-accounts",
1422
1422
  },
@@ -1434,7 +1434,7 @@ const TOOLS = [
1434
1434
  {
1435
1435
  name: "export_publish_youtube",
1436
1436
  description:
1437
- "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. Error code daily_limit or youtube_quota_exceeded (with resetsAt): tell the user when it resets; never retry before resetsAt. Returns { videoUrl, channelId }.",
1437
+ "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. Error code daily_limit or youtube_quota_exceeded (with resetsAt): tell the user when it resets; never retry before resetsAt. privacyStatus defaults to unlisted: public only when the user asked. A vertical or square export of 3 min or less becomes a YouTube Short (result short: true); pass addShortsTag to add #Shorts. Returns { videoUrl, channelId, short }.",
1438
1438
  inputSchema: {
1439
1439
  type: "object",
1440
1440
  properties: {
@@ -1447,6 +1447,10 @@ const TOOLS = [
1447
1447
  categoryId: { type: "string" },
1448
1448
  privacyStatus: { type: "string", enum: ["public", "unlisted", "private"] },
1449
1449
  setThumbnail: { type: "boolean" },
1450
+ addShortsTag: {
1451
+ type: "boolean",
1452
+ description: "Add #Shorts to the description when the export is a Short. Default false.",
1453
+ },
1450
1454
  subtitles: {
1451
1455
  type: "boolean",
1452
1456
  description:
@@ -1463,7 +1467,7 @@ const TOOLS = [
1463
1467
  {
1464
1468
  name: "social_channels",
1465
1469
  description:
1466
- "Social accounts connected in the active workspace (Instagram, TikTok, Facebook, LinkedIn, X) with ids, limits and per-post settings, plus each network's count.",
1470
+ "Social accounts connected in the active workspace (Instagram, TikTok, Facebook, LinkedIn, X) with ids, limits and per-post settings, plus each network's count. X accounts carry xPremium (25,000-char limit when true, else 280). YouTube and YouTube Shorts aren't here: use youtube_list_accounts + export_publish_youtube.",
1467
1471
  inputSchema: { type: "object", properties: {} },
1468
1472
  command: "social.channels",
1469
1473
  },
@@ -1490,6 +1494,20 @@ const TOOLS = [
1490
1494
  },
1491
1495
  command: "social.connect-status",
1492
1496
  },
1497
+ {
1498
+ name: "social_set_x_premium",
1499
+ description:
1500
+ "Mark a connected X account as X Premium (posts up to 25,000 chars) or standard (280). Only when the user says the account has Premium: X rejects long posts from other accounts. Returns the updated channel.",
1501
+ inputSchema: {
1502
+ type: "object",
1503
+ properties: {
1504
+ channelId: { type: "string", description: "The X account's id from social_channels." },
1505
+ premium: { type: "boolean" },
1506
+ },
1507
+ required: ["channelId", "premium"],
1508
+ },
1509
+ command: "social.set-x-premium",
1510
+ },
1493
1511
  {
1494
1512
  name: "social_disconnect",
1495
1513
  description:
@@ -1803,7 +1821,7 @@ const TOOLS = [
1803
1821
  {
1804
1822
  name: "project_edit_summary",
1805
1823
  description:
1806
- "What the edit contains now: lengths (editedDurationMs, sourceDurationMs, removedMs cut or sped up), counts (cuts, zooms, motionElements, graphics, transitions, brollBeats, mediaOverlays, captions { on, template }, audio { narration, music, soundEffects }, voiceEnhancedClips vs loudnessNormalizedClips, ...) and highlights[] (plain lines). Each recording counts once, so splits change neither source length nor cuts. Cheap; use it to report an editing pass instead of project_read.",
1824
+ "What the edit contains now: lengths (editedDurationMs, sourceDurationMs, removedMs cut or sped up), counts (cuts, zooms, motionElements, graphics, transitions, brollBeats, mediaOverlays, captions { on, template }, audio { narration, music, soundEffects }, audioTracks[{id, kind voice|music|sfx, basis}] (the same kind the export mix and audio_preview_mix use), voiceEnhancedClips vs loudnessNormalizedClips, ...) and highlights[] (plain lines). Each recording counts once, so splits change neither source length nor cuts. Cheap; use it to report an editing pass instead of project_read.",
1807
1825
  inputSchema: {
1808
1826
  type: "object",
1809
1827
  properties: {
@@ -2092,14 +2110,17 @@ const TOOLS = [
2092
2110
  ],
2093
2111
  [
2094
2112
  "stop-recording",
2095
- "Stop recording and finalize podcast media. Check podcast_status for finalization and guest uploads.",
2113
+ "Stop recording. The take goes into the episode project by itself (desktop track once finalized, each guest's as their upload lands, even after the studio is left); returns savedTo and takeId. podcast_status shows finalization and guest uploads.",
2096
2114
  ],
2097
2115
  [
2098
2116
  "start-screen-share",
2099
2117
  "Open the desktop picker to share the host screen with everyone in the call (any time, backstage included; it's recorded while recording). One sharer at a time. Waits for the host to choose; fails if the picker is cancelled or capture fails.",
2100
2118
  ],
2101
2119
  ["stop-screen-share", "Stop and finalize the host's podcast screen share."],
2102
- ["open-in-editor", "Open the finished podcast in the editor when guest media has arrived."],
2120
+ [
2121
+ "open-in-editor",
2122
+ "Open the finished podcast's episode in the editor when guest media has arrived (the take is already in the episode project).",
2123
+ ],
2103
2124
  ].map(([noun, description]) => ({
2104
2125
  name: `podcast_${noun.replaceAll("-", "_")}`,
2105
2126
  description,
@@ -2623,6 +2644,34 @@ const TOOLS = [
2623
2644
  },
2624
2645
  command: "podcast.list-upcoming",
2625
2646
  },
2647
+ {
2648
+ name: "podcast_found_recordings",
2649
+ description:
2650
+ "Recordings that belong to a podcast episode but never reached it (older takes left by Back before Open in editor): guest tracks matched by session code plus the nearest desktop camera recording, each with a key. No path / id / sessionCode = every episode.",
2651
+ inputSchema: {
2652
+ type: "object",
2653
+ properties: {
2654
+ path: { type: "string" },
2655
+ id: { type: "string" },
2656
+ sessionCode: { type: "string" },
2657
+ },
2658
+ },
2659
+ command: "podcast.found-recordings",
2660
+ },
2661
+ {
2662
+ name: "podcast_add_found_recordings",
2663
+ description:
2664
+ "Add a podcast_found_recordings take (path + key) to its episode: it becomes the episode's podcast clip, files move into the project folder. Matched by time, so ask the user first; check the sync afterwards (project_auto_sync_podcast).",
2665
+ inputSchema: {
2666
+ type: "object",
2667
+ properties: {
2668
+ path: { type: "string", description: "The episode's project file." },
2669
+ key: { type: "string", description: "The found take's key." },
2670
+ },
2671
+ required: ["path", "key"],
2672
+ },
2673
+ command: "podcast.add-found-recordings",
2674
+ },
2626
2675
  {
2627
2676
  name: "podcast_get_episode",
2628
2677
  description:
@@ -3698,6 +3747,39 @@ const TOOLS = [
3698
3747
  },
3699
3748
  command: "project.remove-mute-region",
3700
3749
  },
3750
+ {
3751
+ name: "project_add_original_voice",
3752
+ description:
3753
+ 'Turn Enhance voice off for an edited-time span ("Original voice"): the main track plays its original audio there, enhanced elsewhere. Only enhanced clips change. Returns regionId (editor.enhanceOffRegions).',
3754
+ inputSchema: {
3755
+ type: "object",
3756
+ properties: {
3757
+ id: { type: "string" },
3758
+ path: { type: "string" },
3759
+ atMs: { type: "number" },
3760
+ durationMs: { type: "number" },
3761
+ expectedRevision: { type: "number" },
3762
+ },
3763
+ required: ["atMs", "durationMs"],
3764
+ },
3765
+ command: "project.add-original-voice",
3766
+ },
3767
+ {
3768
+ name: "project_remove_original_voice",
3769
+ description:
3770
+ "Remove an Original voice region so Enhance voice applies there again (ids: editor.enhanceOffRegions[].id).",
3771
+ inputSchema: {
3772
+ type: "object",
3773
+ properties: {
3774
+ id: { type: "string" },
3775
+ path: { type: "string" },
3776
+ regionId: { type: "string" },
3777
+ expectedRevision: { type: "number" },
3778
+ },
3779
+ required: ["regionId"],
3780
+ },
3781
+ command: "project.remove-original-voice",
3782
+ },
3701
3783
  {
3702
3784
  name: "project_add_bleep",
3703
3785
  description:
@@ -4143,7 +4225,7 @@ const TOOLS = [
4143
4225
  {
4144
4226
  name: "project_review_moment",
4145
4227
  description:
4146
- "LOOK AND LISTEN to a moment of the edit as the viewer gets it: a contact sheet of the frames around atMs (edited ms) or wordId, the EXPORTED mix of that stretch transcribed and compared with what the edit should play (extra = leftover of a cut word or line, missing = clipped word), whether each cut lands in quiet or speech, nearby edits and stutters, plus a plain verdict. Use after every cut / pause / insert fix BEFORE telling the user it's fixed, and when the user names a moment ('at 16 seconds'). ASYNC: { jobId }; job_wait.",
4228
+ "LOOK AND LISTEN to a moment of the edit as the viewer gets it: a contact sheet of the frames around atMs (edited ms) or wordId, the EXPORTED mix of that stretch transcribed and compared with what the edit should play (the clips' words AND narration / voiceover track words; extra = leftover of a cut word or line, missing = clipped word), whether each cut that interrupts the main track's sound lands in quiet or speech (judged on the main track alone, so narration or music playing across it doesn't count; a join of silent clips such as images is never a cut in the speech), nearby edits and stutters, plus a plain verdict. Use after every cut / pause / insert fix BEFORE telling the user it's fixed, and when the user names a moment ('at 16 seconds'). ASYNC: { jobId }; job_wait.",
4147
4229
  inputSchema: {
4148
4230
  type: "object",
4149
4231
  properties: {
@@ -4198,7 +4280,7 @@ const TOOLS = [
4198
4280
  {
4199
4281
  name: "audio_preview_mix",
4200
4282
  description:
4201
- "Render final export audio only to temporary .m4a plus description and voice/music/SFX stem RMS/masking flags. Edited-time range fromMs..toMs inside 0..editedDurationMs (project_read), default whole project, max 10 minutes. Async: job.wait.",
4283
+ "Render final export audio only to temporary .m4a plus description and voice/music/SFX stem RMS/masking flags; tracks[{id, kind, basis}] = which stem each audio track went to (same as project_edit_summary audioTracks). Edited-time range fromMs..toMs inside 0..editedDurationMs (project_read), default whole project, max 10 minutes. Async: job.wait.",
4202
4284
  inputSchema: {
4203
4285
  type: "object",
4204
4286
  properties: {
@@ -5126,7 +5208,7 @@ const TOOLS = [
5126
5208
  {
5127
5209
  name: "project_auto_reframe",
5128
5210
  description:
5129
- "Active-speaker auto-reframe: frames whoever talks, shot by shot (face tracking, no speaker IDs needed). Landscape footage on a vertical / square canvas gets a crop that follows the speaker; footage that already has the canvas shape (a 16:9 wide of two hosts in a 16:9 video) gets a punch-in on the speaker inside the frame, which is also how to get face-tracked zooms on the main video. zoom = fixed punch-in. 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).",
5211
+ "Active-speaker auto-reframe: frames whoever talks, shot by shot (face tracking, no speaker IDs needed). Landscape footage on a vertical / square canvas gets a crop that follows the speaker; footage that already has the canvas shape (a 16:9 wide of two hosts in a 16:9 video) gets a punch-in on the speaker inside the frame, which is also how to get face-tracked zooms on the main video. zoom = fixed punch-in. split=true stacks two people (podcast / interview wide) one above the other on a 9:16 canvas (not on a clip with its own camera video). In the vertical-stack camera layout the crop is computed for the main video's top half. 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).",
5130
5212
  inputSchema: {
5131
5213
  type: "object",
5132
5214
  properties: {
@@ -5396,6 +5478,12 @@ const TOOLS = [
5396
5478
  type: "boolean",
5397
5479
  description: "Repeat the file to endMs (a ~30 s Lyria clip under a longer video).",
5398
5480
  },
5481
+ audioKind: {
5482
+ type: "string",
5483
+ enum: ["voice", "music", "sfx"],
5484
+ description:
5485
+ "What the track is (its mix bus, preview-mix stem and edit-summary count). Omit for bundled library files and generated narration / music / SFX (known); pass it for other files, e.g. music for an uploaded song. Change later: project_update_region regionType=audio-overlay patch {audioKind}.",
5486
+ },
5399
5487
  transcribe: {
5400
5488
  type: "boolean",
5401
5489
  description:
@@ -5807,7 +5895,7 @@ const TOOLS = [
5807
5895
  {
5808
5896
  name: "project_set_clip_webcam",
5809
5897
  description:
5810
- "Attach, replace or remove a clip's camera video (Screen + camera), in sync from its first frame. Not for podcast composites.",
5898
+ "Attach, replace or remove a clip's camera video, in sync from its first frame: a presenter camera over a screen recording, or a REACTION video (main clip = the video reacted to, camera = the reactor; audio=mix plays both). The main video keeps its kind. In 9:16 use set-webcam-layout vertical-stack (main on top, camera below), then auto-reframe frames the main video inside its half. Not for podcast composites.",
5811
5899
  inputSchema: {
5812
5900
  type: "object",
5813
5901
  properties: {
@@ -5818,8 +5906,9 @@ const TOOLS = [
5818
5906
  webcam: { type: "string", description: "Absolute path, or '' to remove." },
5819
5907
  audio: {
5820
5908
  type: "string",
5821
- enum: ["auto", "camera", "keep"],
5822
- description: "auto: camera audio if the main video is silent (re-transcribe after).",
5909
+ enum: ["auto", "mix", "camera", "keep"],
5910
+ description:
5911
+ "auto: camera audio if the main video is silent; mix: both sounds (reaction video); camera: camera's sound only; keep: unchanged. Re-transcribe after a change.",
5823
5912
  },
5824
5913
  expectedRevision: { type: "number" },
5825
5914
  },
@@ -5895,13 +5984,14 @@ const TOOLS = [
5895
5984
  {
5896
5985
  name: "system_set_transcription_preferences",
5897
5986
  description:
5898
- "Save workspace transcription language, provider and/or on-device engine atomically (pass any). engine: auto (Parakeet for English/auto-detect, Whisper LOCKED to any other named language), parakeet (keeps fillers, detects the language, can't be locked), whisper (locks to the language). Confirm consent to upload audio for cloud. Cloud and apple cannot use auto.",
5987
+ "Save workspace transcription language, provider and/or on-device engine atomically (pass any). engine: auto (Parakeet for English/auto-detect, Whisper LOCKED to any other named language), parakeet (keeps fillers, detects the language, can't be locked), whisper (locks to the language). Confirm consent to upload audio for cloud. Cloud and apple cannot use auto. fallback: when the cloud provider / Apple speech still fails after retries, local (default: this computer transcribes only the failed parts, reported) or none (fail, never transcribe on this computer: use when the user wants the cloud provider only).",
5899
5988
  inputSchema: {
5900
5989
  type: "object",
5901
5990
  properties: {
5902
5991
  language: { type: "string" },
5903
5992
  provider: { type: "string", enum: ["local", "apple", "deepgram", "elevenlabs"] },
5904
5993
  engine: { type: "string", enum: ["auto", "parakeet", "whisper"] },
5994
+ fallback: { type: "string", enum: ["local", "none"] },
5905
5995
  },
5906
5996
  },
5907
5997
  command: "system.set-transcription-preferences",
@@ -5910,13 +6000,19 @@ const TOOLS = [
5910
6000
  {
5911
6001
  name: "transcript_transcribe",
5912
6002
  description:
5913
- "Transcribe clips with word timestamps (default: only clips without words). Asking again while the same transcription is still running returns that job (alreadyRunning: true), never a second run: keep waiting on it. Engine: language auto/english = Parakeet; any other named language = local Whisper LOCKED to it (engine override). Returns { jobId, plan } (plan = engine + reason, ranges, totalAudioMs: the audio that will be processed, estimate/estimatedMs: how long on this computer, advice: faster options for a long Whisper run; tell the user for long jobs). Long Whisper runs are saved chunk by chunk: stalls restart from the last chunk automatically; after job_cancel, a crash or an app restart, transcribing again resumes there. Progress is real (audio decoded); time left in progress.message and progress.data.etaMs. 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 tail trim, e.g. one half of a split; only the kept parts when cuts hide most of a long recording; result transcribedRanges[]). Clips with no audio track are skipped (result noAudioClips), not failed. Result warnings[] flags words that can't cover the audio (sound with no words, too few words: music, noise, another language) and a re-run with the same words as before: surface it, and get a second reading with engine whisper or a cloud provider (Parakeet isn't locked by language english, so retrying the same way repeats it). 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).",
6003
+ "Transcribe clips with word timestamps (default: only clips without words). Asking again while the same transcription is still running returns that job (alreadyRunning: true), never a second run: keep waiting on it. Engine: language auto/english = Parakeet; any other named language = local Whisper LOCKED to it (engine override). Returns { jobId, plan } (plan = engine + reason, ranges, totalAudioMs: the audio that will be processed, estimate/estimatedMs: how long on this computer, advice: faster options for a long Whisper run; tell the user for long jobs). Long Whisper runs are saved chunk by chunk: stalls restart from the last chunk automatically; after job_cancel, a crash or an app restart, transcribing again resumes there. Progress is real (audio decoded); time left in progress.message and progress.data.etaMs. 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 tail trim, e.g. one half of a split; only the kept parts when cuts hide most of a long recording; result transcribedRanges[]). Clips with no audio track are skipped (result noAudioClips), not failed. Result warnings[] flags words that can't cover the audio (sound with no words, too few words: music, noise, another language) and a re-run with the same words as before: surface it, and get a second reading with engine whisper or a cloud provider (Parakeet isn't locked by language english, so retrying the same way repeats it). 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). CLOUD (deepgram / elevenlabs): long audio goes up in ~5 min parts, each retried on network drops, timeouts, 5xx and 429. fallback: none = parts that still fail FAIL the job (job.errorDetails.code cloud_failed, failedRanges; nothing runs on this computer; parts that worked are kept for a re-run), local = this computer transcribes only the failed parts. Default none when you pass a cloud/apple provider, else the workspace setting. The job says which engine really ran: progress.data.engine / fallbackFrom while running, result clips[].engine / engines / fallback. job_cancel stops every upload, retry and on-device helper of the job.",
5914
6004
  inputSchema: {
5915
6005
  type: "object",
5916
6006
  properties: {
5917
6007
  id: { type: "string" },
5918
6008
  path: { type: "string" },
5919
6009
  clipId: { type: "string", description: "Only this clip, even if already transcribed." },
6010
+ fallback: {
6011
+ type: "string",
6012
+ enum: ["local", "none"],
6013
+ description:
6014
+ "When the cloud provider / Apple speech still fails after retries: none = fail the job (never transcribe on this computer), local = this computer transcribes only the failed parts. Default: none when provider is passed, else the workspace setting.",
6015
+ },
5920
6016
  language: {
5921
6017
  type: "string",
5922
6018
  description:
@@ -8676,13 +8772,18 @@ const TOOLS = [
8676
8772
  {
8677
8773
  name: "llm_generate_title",
8678
8774
  description:
8679
- "Generate a YouTube title from what the EDITED video says (cut words left out) with the local LLM. Returns { title }: one complete phrase within maxChars, never cut mid-word.",
8775
+ "Generate a YouTube title from what the EDITED video says (cut words left out) with the local LLM. Names the people speaking when the project knows them (podcast participants); instruction steers it. Returns { title }: one complete phrase within maxChars, never cut mid-word.",
8680
8776
  inputSchema: {
8681
8777
  type: "object",
8682
8778
  properties: {
8683
8779
  id: { type: "string" },
8684
8780
  path: { type: "string" },
8685
8781
  maxChars: { type: "number" },
8782
+ instruction: {
8783
+ type: "string",
8784
+ description:
8785
+ "Optional steer from the user (what to focus on), max 500 chars. Speaker names the project knows are used automatically.",
8786
+ },
8686
8787
  },
8687
8788
  },
8688
8789
  command: "llm.generate-title",
@@ -8690,13 +8791,18 @@ const TOOLS = [
8690
8791
  {
8691
8792
  name: "llm_generate_description",
8692
8793
  description:
8693
- "Generate a 3-5 sentence YouTube description from what the EDITED video says, with the local LLM. Returns { description, warnings? }: a warning means it repeated the transcript, so write the description yourself.",
8794
+ "Generate a 3-5 sentence YouTube description from what the EDITED video says, with the local LLM. Names the people speaking when the project knows them (podcast participants); instruction steers what it focuses on. Returns { description, warnings? }: a warning means it repeated the transcript, so write the description yourself.",
8694
8795
  inputSchema: {
8695
8796
  type: "object",
8696
8797
  properties: {
8697
8798
  id: { type: "string" },
8698
8799
  path: { type: "string" },
8699
8800
  maxChars: { type: "number" },
8801
+ instruction: {
8802
+ type: "string",
8803
+ description:
8804
+ "Optional steer from the user (what to focus on), max 500 chars. Speaker names the project knows are used automatically.",
8805
+ },
8700
8806
  },
8701
8807
  },
8702
8808
  command: "llm.generate-description",
@@ -8725,6 +8831,11 @@ const TOOLS = [
8725
8831
  id: { type: "string" },
8726
8832
  path: { type: "string" },
8727
8833
  maxChapters: { type: "number" },
8834
+ instruction: {
8835
+ type: "string",
8836
+ description:
8837
+ "Optional steer from the user (what to focus on), max 500 chars. Speaker names the project knows are used automatically.",
8838
+ },
8728
8839
  },
8729
8840
  },
8730
8841
  command: "llm.generate-timestamps",
@@ -8939,7 +9050,7 @@ const TOOLS = [
8939
9050
  {
8940
9051
  name: "clips_export",
8941
9052
  description:
8942
- "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[] }.",
9053
+ "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[{ clipId, outputPath, exportId }] }. To publish a clip, export it here, then pass exportId to export_publish_youtube (9:16 = Short) or export_publish_social.",
8943
9054
  inputSchema: {
8944
9055
  type: "object",
8945
9056
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@writepanda/mcp",
3
- "version": "1.285.0",
3
+ "version": "1.286.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",