@writepanda/mcp 1.33.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.
- package/bin/server.mjs +170 -7
- 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,
|
|
@@ -382,6 +413,24 @@ const TOOLS = [
|
|
|
382
413
|
},
|
|
383
414
|
command: "project.list",
|
|
384
415
|
},
|
|
416
|
+
{
|
|
417
|
+
name: "project_set_folder",
|
|
418
|
+
description:
|
|
419
|
+
'Move a project to a folder (organisational label shown on the Home page) or back to Unsorted. Folders are flat (no nesting) and scoped to the project\'s owning workspace. Identify the project by `id` (preferred) or `path`. Pass `folder: ""` (empty string) or omit the field to clear the folder and return the project to Unsorted. Returns { id, path, folder }. Use this when the user asks to organise, sort, label, group, archive, or categorise projects.',
|
|
420
|
+
inputSchema: {
|
|
421
|
+
type: "object",
|
|
422
|
+
properties: {
|
|
423
|
+
id: { type: "string", description: "Project UUID (preferred)." },
|
|
424
|
+
path: { type: "string", description: "Absolute project file path." },
|
|
425
|
+
folder: {
|
|
426
|
+
type: "string",
|
|
427
|
+
description:
|
|
428
|
+
"Folder label. Case-sensitive; whitespace is trimmed. Empty string or omitted = move to Unsorted.",
|
|
429
|
+
},
|
|
430
|
+
},
|
|
431
|
+
},
|
|
432
|
+
command: "project.setFolder",
|
|
433
|
+
},
|
|
385
434
|
{
|
|
386
435
|
name: "project_locate",
|
|
387
436
|
description:
|
|
@@ -418,7 +467,7 @@ const TOOLS = [
|
|
|
418
467
|
{
|
|
419
468
|
name: "project_read",
|
|
420
469
|
description:
|
|
421
|
-
"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.",
|
|
422
471
|
inputSchema: {
|
|
423
472
|
type: "object",
|
|
424
473
|
properties: {
|
|
@@ -489,16 +538,21 @@ const TOOLS = [
|
|
|
489
538
|
{
|
|
490
539
|
name: "project_add_motion_graphic",
|
|
491
540
|
description:
|
|
492
|
-
"Drop a motion-graphic MP4 onto the timeline.
|
|
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.",
|
|
493
542
|
inputSchema: {
|
|
494
543
|
type: "object",
|
|
495
544
|
properties: {
|
|
496
545
|
id: { type: "string" },
|
|
497
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
|
+
},
|
|
498
552
|
file: {
|
|
499
553
|
type: "string",
|
|
500
554
|
description:
|
|
501
|
-
"Absolute path to
|
|
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 job — never construct paths manually (they get truncated at the space in `Application Support` and the overlay silently fails). One of `fromJobId` or `file` is required.",
|
|
502
556
|
},
|
|
503
557
|
durationMs: { type: "number" },
|
|
504
558
|
atMs: { type: "number", description: "Default: end of timeline" },
|
|
@@ -533,10 +587,71 @@ const TOOLS = [
|
|
|
533
587
|
},
|
|
534
588
|
expectedRevision: { type: "number" },
|
|
535
589
|
},
|
|
536
|
-
required: ["
|
|
590
|
+
required: ["durationMs"],
|
|
537
591
|
},
|
|
538
592
|
command: "project.add-motion-graphic",
|
|
539
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
|
+
},
|
|
540
655
|
{
|
|
541
656
|
name: "project_set_overlay_backdrop_blur",
|
|
542
657
|
description:
|
|
@@ -1395,13 +1510,26 @@ const TOOLS = [
|
|
|
1395
1510
|
{
|
|
1396
1511
|
name: "caption_set_template",
|
|
1397
1512
|
description:
|
|
1398
|
-
"Pick a caption template: classic, modern, minimal, bold, spotlight, boxed, neon, or
|
|
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).",
|
|
1399
1514
|
inputSchema: {
|
|
1400
1515
|
type: "object",
|
|
1401
1516
|
properties: {
|
|
1402
1517
|
id: { type: "string" },
|
|
1403
1518
|
path: { type: "string" },
|
|
1404
|
-
templateId: {
|
|
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
|
+
},
|
|
1405
1533
|
expectedRevision: { type: "number" },
|
|
1406
1534
|
},
|
|
1407
1535
|
required: ["templateId"],
|
|
@@ -1436,6 +1564,11 @@ const TOOLS = [
|
|
|
1436
1564
|
strokeColor: { type: "string", description: "Text stroke/outline color" },
|
|
1437
1565
|
strokeWidth: { type: "number", description: "Text stroke width in px" },
|
|
1438
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
|
+
},
|
|
1439
1572
|
expectedRevision: { type: "number" },
|
|
1440
1573
|
},
|
|
1441
1574
|
},
|
|
@@ -1850,6 +1983,19 @@ const TOOLS = [
|
|
|
1850
1983
|
},
|
|
1851
1984
|
|
|
1852
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
|
+
},
|
|
1853
1999
|
{
|
|
1854
2000
|
name: "job_wait",
|
|
1855
2001
|
description:
|
|
@@ -1878,6 +2024,23 @@ const TOOLS = [
|
|
|
1878
2024
|
command: "job.get",
|
|
1879
2025
|
},
|
|
1880
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
|
+
|
|
1881
2044
|
// ── escape hatch ────────────────────────────────────────────────
|
|
1882
2045
|
{
|
|
1883
2046
|
name: "pandastudio_call",
|
|
@@ -1900,7 +2063,7 @@ const TOOLS = [
|
|
|
1900
2063
|
const server = new Server(
|
|
1901
2064
|
{
|
|
1902
2065
|
name: "pandastudio",
|
|
1903
|
-
version: "1.
|
|
2066
|
+
version: "1.19.0",
|
|
1904
2067
|
},
|
|
1905
2068
|
{
|
|
1906
2069
|
capabilities: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@writepanda/mcp",
|
|
3
|
-
"version": "1.
|
|
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",
|