@writepanda/mcp 1.34.0 → 1.38.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 +152 -7
  2. package/package.json +1 -1
package/bin/server.mjs CHANGED
@@ -162,6 +162,37 @@ const TOOLS = [
162
162
  inputSchema: { type: "object", properties: {} },
163
163
  command: "system.list",
164
164
  },
165
+ {
166
+ name: "system_get_transcription_language",
167
+ description:
168
+ "Read the active transcription engine selection for the current workspace. Returns { language } where language is one of: 'auto' (Parakeet TDT v3, default — auto-detects English + 25 European languages including Russian/Ukrainian), 'chinese' / 'japanese' / 'korean' / 'hindi' / 'arabic' / 'thai' (each routes to Whisper Large-v3-turbo). Use this when surfacing the active engine to the user or before recommending a switch.",
169
+ inputSchema: { type: "object", properties: {} },
170
+ command: "system.getTranscriptionLanguage",
171
+ },
172
+ {
173
+ name: "system_set_transcription_language",
174
+ description:
175
+ "Switch the transcription engine for the current workspace. Default 'auto' uses Parakeet (English + 25 European languages, auto-detected, word-level timestamps native). Non-'auto' values route to Whisper Large-v3-turbo with that language locked in. **Important**: non-'auto' choices require a one-time ~1.1 GB Whisper model download — call `system_is_whisper_model_downloaded` first, and surface the download requirement to the user before switching. Use this when the user explicitly asks for a non-European transcription language (e.g., 'transcribe this in Chinese'), or when project content clearly indicates a different language than the current setting.",
176
+ inputSchema: {
177
+ type: "object",
178
+ properties: {
179
+ language: {
180
+ type: "string",
181
+ enum: ["auto", "chinese", "japanese", "korean", "hindi", "arabic", "thai"],
182
+ description: "Target language. 'auto' = Parakeet (default); the others route to Whisper.",
183
+ },
184
+ },
185
+ required: ["language"],
186
+ },
187
+ command: "system.setTranscriptionLanguage",
188
+ },
189
+ {
190
+ name: "system_is_whisper_model_downloaded",
191
+ description:
192
+ "Check whether the Whisper Large-v3-turbo model has been downloaded yet. Returns { downloaded: boolean }. ALWAYS call this before `system_set_transcription_language` to a non-'auto' value — Whisper transcription fails with a structured error if the model isn't on disk, and surprising the user with a 1.1 GB download mid-flow is bad UX.",
193
+ inputSchema: { type: "object", properties: {} },
194
+ command: "system.isWhisperModelDownloaded",
195
+ },
165
196
 
166
197
  // ── workspaces (v1.19) ──────────────────────────────────────────
167
198
  // Multi-workspace isolation: each workspace has its own projects,
@@ -436,7 +467,7 @@ const TOOLS = [
436
467
  {
437
468
  name: "project_read",
438
469
  description:
439
- "Read a project's full JSON. Returns { path, project, clipStates, workspaceId, workspaceName, isInActiveWorkspace }. `clipStates` is a per-clip summary: { clipId, mediaPath, durationMs, transcribed, wordCount, audioCleaned, cleanedAudioPath? } — use it to decide whether to call transcript_transcribe or audio_clean before editing. **Workspace fields:** every read includes the project's owning workspace; if `isInActiveWorkspace` is false, the YouTube account and Replicate key in scope are NOT the ones that own this project — surface the mismatch to the user before any export or publish. Pass `project.revision` back as `expectedRevision` on subsequent writes for conflict-safe edits. **Performance tip:** pass `includeTranscript: false` after your first read to drop the per-clip transcript words from the response — they're typically 600+ KB on a 5-minute recording and most agent flows don't need them after pacing is done. clipStates always tells you the transcribed/wordCount status either way.",
470
+ "Read a project's full JSON. Returns { path, project, clipStates, workspaceId, workspaceName, isInActiveWorkspace }. `clipStates` is a per-clip summary: { clipId, mediaPath, durationMs, transcribed, wordCount, audioCleaned, cleanedAudioPath? } — use it to decide whether to call transcript_transcribe or audio_clean before editing. **Workspace fields:** every read includes the project's owning workspace; if `isInActiveWorkspace` is false, the YouTube account and Replicate key in scope are NOT the ones that own this project — surface the mismatch to the user before any export or publish. Pass `project.revision` back as `expectedRevision` on subsequent writes for conflict-safe edits. **Performance tip:** pass `includeTranscript: false` after your first read to drop the per-clip transcript words from the response — they're typically 600+ KB on a 5-minute recording and most agent flows don't need them after pacing is done. clipStates always tells you the transcribed/wordCount status either way. Clips at project.mainTrack.clips[], overlays at project.editor.mediaOverlayRegions[]. Response includes editedDurationMs, sourceDurationMs, totalTrimmedMs, trimCount.",
440
471
  inputSchema: {
441
472
  type: "object",
442
473
  properties: {
@@ -507,16 +538,21 @@ const TOOLS = [
507
538
  {
508
539
  name: "project_add_motion_graphic",
509
540
  description:
510
- "Drop a motion-graphic MP4 onto the timeline. The `file` argument MUST be the exact `outputPath` returned by a prior motion_render_html / motion_generate / motion_concat / motion_screenshot job NEVER hand-author or guess this path. Optional SFX plays when it appears.",
541
+ "Drop a motion-graphic MP4/WebM onto the timeline. PREFERRED: pass `fromJob` (the jobId from motion_render_html / motion_generate / motion_concat) — the server resolves the outputPath internally, so you never handle the path string at all. Fallback: pass `file` (an absolute path) only for external/uploaded files. Optional SFX plays when it appears.",
511
542
  inputSchema: {
512
543
  type: "object",
513
544
  properties: {
514
545
  id: { type: "string" },
515
546
  path: { type: "string" },
547
+ fromJob: {
548
+ type: "string",
549
+ description:
550
+ "PREFERRED for render outputs. The jobId returned by motion_render_html / motion_generate / motion_concat. The server reads the outputPath the job produced — you never construct or pass a path, eliminating the path-truncation failure mode entirely. The job must have succeeded (call job_wait first). Mutually exclusive with `file`.",
551
+ },
516
552
  file: {
517
553
  type: "string",
518
554
  description:
519
- "Absolute path to the rendered MP4/WebM. MUST be the exact `outputPath` returned by motion_render_html (or motion_generate / motion_concat / motion_screenshot). Do NOT construct paths manuallyguessed paths get truncated at whitespace (e.g. `/Users/.../Library/Application` from `Application Support`) and the overlay flickers and disappears in preview. If you don't have an outputPath, render one first.",
555
+ "Absolute path to an external MP4/WebM (e.g. a user upload). For RENDER outputs use `fromJobId` instead. If you must use `file`, pass the exact `outputPath` from a render jobnever construct paths manually (they get truncated at the space in `Application Support` and the overlay silently fails). One of `fromJobId` or `file` is required.",
520
556
  },
521
557
  durationMs: { type: "number" },
522
558
  atMs: { type: "number", description: "Default: end of timeline" },
@@ -551,10 +587,71 @@ const TOOLS = [
551
587
  },
552
588
  expectedRevision: { type: "number" },
553
589
  },
554
- required: ["file", "durationMs"],
590
+ required: ["durationMs"],
555
591
  },
556
592
  command: "project.add-motion-graphic",
557
593
  },
594
+ {
595
+ name: "project_add_designed_segment",
596
+ description:
597
+ "Create a 'designed segment' — the YouTube split where the host stays LIVE on one half and a motion-graphic panel fills the other (host right / panel left, like a Vox or MKBHD explainer beat). ONE atomic call places both pieces over the same time range with the same transition and source anchor, so they can't drift or mismatch: the camera is repositioned + cover-cropped into its half (a full-bleed split clip-transform) and a full-frame transparent panel graphic is composited on top. Stays in the live compositor — camera is full-quality and scrubbable, nothing baked. IMPORTANT: author the panel graphic as a FULL-FRAME (1920×1080) TRANSPARENT render via motion_render_html --transparent — paint the opaque panel + content on the panel side, leave the camera's half fully transparent (it reveals the repositioned host). See SKILL.md 'Designed segments'.",
598
+ inputSchema: {
599
+ type: "object",
600
+ properties: {
601
+ id: { type: "string" },
602
+ path: { type: "string" },
603
+ fromJob: {
604
+ type: "string",
605
+ description:
606
+ "PREFERRED. The jobId from the transparent motion_render_html job (the full-frame panel graphic with the camera half transparent). Server resolves the outputPath internally — no shell, no path truncation. Mutually exclusive with `file`.",
607
+ },
608
+ file: {
609
+ type: "string",
610
+ description:
611
+ "Absolute path to the transparent panel graphic (WebM/MOV with alpha). Prefer `fromJob`; if used, pass a render job's exact outputPath — never hand-construct (truncates at the space in 'Application Support').",
612
+ },
613
+ durationMs: { type: "number" },
614
+ atMs: { type: "number", description: "Default: end of timeline" },
615
+ cameraSide: {
616
+ type: "string",
617
+ enum: ["left", "right"],
618
+ description:
619
+ "Which half the CAMERA fills; the panel graphic fills the opposite half. 'right' is the canonical reference (host right, panel left).",
620
+ },
621
+ cameraRatio: {
622
+ type: "number",
623
+ enum: [50, 55],
624
+ description:
625
+ "Camera's share of the frame width: 55 (default, the reference split) or 50 (balanced). Panel gets the remaining 45 or 50. Author the panel's opaque width to match (e.g. left 45% for cameraSide=right, cameraRatio=55).",
626
+ },
627
+ transitionMs: {
628
+ type: "number",
629
+ description: "Slide-in/out window at each edge in ms. Default 320.",
630
+ },
631
+ soundUrl: {
632
+ type: "string",
633
+ description:
634
+ "Optional SFX when the segment appears. Bundled id (bundled:sound/<id>), absolute path, or file:// URL. Use asset_list_sounds to discover.",
635
+ },
636
+ soundVolume: {
637
+ type: "number",
638
+ description: "Sound volume 0–1. Default 1 when soundUrl is set.",
639
+ },
640
+ anchorSourceMs: {
641
+ type: "number",
642
+ description:
643
+ "Anchor BOTH regions to a SOURCE-time moment (transcript word startMs). Pass when atMs was derived from a transcript word so the whole segment stays glued across later transcript edits. Omit for free-floating placement.",
644
+ },
645
+ anchorSourceEndMs: {
646
+ type: "number",
647
+ description: "Optional anchor end (source ms) for ranged anchoring.",
648
+ },
649
+ expectedRevision: { type: "number" },
650
+ },
651
+ required: ["durationMs", "cameraSide"],
652
+ },
653
+ command: "project.add-designed-segment",
654
+ },
558
655
  {
559
656
  name: "project_set_overlay_backdrop_blur",
560
657
  description:
@@ -1413,13 +1510,26 @@ const TOOLS = [
1413
1510
  {
1414
1511
  name: "caption_set_template",
1415
1512
  description:
1416
- "Pick a caption template: classic, modern, minimal, bold, spotlight, boxed, neon, or colored.",
1513
+ "Pick a caption template: classic, modern, minimal, bold, spotlight, boxed, neon, colored, or texture. `texture` fills large uppercase words with a flowing texture mask (lava/marble/metal/wood/concrete/rock) — pick the texture via caption_set_style `texture` (defaults to lava).",
1417
1514
  inputSchema: {
1418
1515
  type: "object",
1419
1516
  properties: {
1420
1517
  id: { type: "string" },
1421
1518
  path: { type: "string" },
1422
- templateId: { type: "string" },
1519
+ templateId: {
1520
+ type: "string",
1521
+ enum: [
1522
+ "classic",
1523
+ "modern",
1524
+ "minimal",
1525
+ "bold",
1526
+ "spotlight",
1527
+ "boxed",
1528
+ "neon",
1529
+ "colored",
1530
+ "texture",
1531
+ ],
1532
+ },
1423
1533
  expectedRevision: { type: "number" },
1424
1534
  },
1425
1535
  required: ["templateId"],
@@ -1454,6 +1564,11 @@ const TOOLS = [
1454
1564
  strokeColor: { type: "string", description: "Text stroke/outline color" },
1455
1565
  strokeWidth: { type: "number", description: "Text stroke width in px" },
1456
1566
  fontSize: { type: "string", description: "CSS font-size value, e.g. '28px'" },
1567
+ texture: {
1568
+ type: "string",
1569
+ enum: ["lava", "marble", "metal", "wood", "concrete", "rock"],
1570
+ description: "Texture for the 'texture' caption template. Ignored by other templates.",
1571
+ },
1457
1572
  expectedRevision: { type: "number" },
1458
1573
  },
1459
1574
  },
@@ -1868,6 +1983,19 @@ const TOOLS = [
1868
1983
  },
1869
1984
 
1870
1985
  // ── async jobs ─────────────────────────────────────────────────
1986
+ {
1987
+ name: "job_cancel",
1988
+ description:
1989
+ "Cancel a running or queued job. Idempotent — already-terminal jobs are returned as-is.",
1990
+ inputSchema: {
1991
+ type: "object",
1992
+ properties: {
1993
+ id: { type: "string", description: "Job id" },
1994
+ },
1995
+ required: ["id"],
1996
+ },
1997
+ command: "job.cancel",
1998
+ },
1871
1999
  {
1872
2000
  name: "job_wait",
1873
2001
  description:
@@ -1896,6 +2024,23 @@ const TOOLS = [
1896
2024
  command: "job.get",
1897
2025
  },
1898
2026
 
2027
+ // ── timeline helpers ────────────────────────────────────────────
2028
+ {
2029
+ name: "timeline_source_to_edited",
2030
+ description:
2031
+ "Convert source-time ms to edited-timeline ms, accounting for trims and speed regions. Returns null if inside a trimmed region.",
2032
+ inputSchema: {
2033
+ type: "object",
2034
+ properties: {
2035
+ id: { type: "string" },
2036
+ path: { type: "string" },
2037
+ sourceMs: { type: "number", description: "Source-time position in ms" },
2038
+ },
2039
+ required: ["sourceMs"],
2040
+ },
2041
+ command: "timeline.source-to-edited",
2042
+ },
2043
+
1899
2044
  // ── escape hatch ────────────────────────────────────────────────
1900
2045
  {
1901
2046
  name: "pandastudio_call",
@@ -1918,7 +2063,7 @@ const TOOLS = [
1918
2063
  const server = new Server(
1919
2064
  {
1920
2065
  name: "pandastudio",
1921
- version: "1.17.0",
2066
+ version: "1.19.0",
1922
2067
  },
1923
2068
  {
1924
2069
  capabilities: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@writepanda/mcp",
3
- "version": "1.34.0",
3
+ "version": "1.38.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",