@writepanda/mcp 1.265.0 → 1.269.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 +88 -9
  2. package/package.json +1 -1
package/bin/server.mjs CHANGED
@@ -1739,7 +1739,7 @@ const TOOLS = [
1739
1739
  {
1740
1740
  name: "project_edit_summary",
1741
1741
  description:
1742
- "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 }, ...) and highlights[] (plain lines). Cheap; use it to report an editing pass instead of project_read.",
1742
+ "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.",
1743
1743
  inputSchema: {
1744
1744
  type: "object",
1745
1745
  properties: {
@@ -3576,6 +3576,35 @@ const TOOLS = [
3576
3576
  },
3577
3577
  command: "project.add-spotlight",
3578
3578
  },
3579
+ {
3580
+ name: "project_set_speaker_background",
3581
+ description:
3582
+ "The speaker's background for the WHOLE video as one look: blur, remove (wallpaper shows; outline on by default), image (studio plate) or none. Replaces the previous whole-video background instead of stacking; partial background-effect regions stay. Camera footage. For one stretch use project_add_background_effect.",
3583
+ inputSchema: {
3584
+ type: "object",
3585
+ properties: {
3586
+ id: { type: "string" },
3587
+ path: { type: "string" },
3588
+ mode: { type: "string", enum: ["none", "blur", "remove", "image"] },
3589
+ strength: { type: "number", description: "blur: 1-120, default 18." },
3590
+ backgroundImage: {
3591
+ type: "string",
3592
+ description:
3593
+ "image: warm-creator, tech-rgb, neutral-grey, podcast-warm, daylight-airy, gradient-gel, cinematic-dark, or an image path.",
3594
+ },
3595
+ outline: {
3596
+ type: "boolean",
3597
+ description: "Keyline around the cutout (on by default for remove).",
3598
+ },
3599
+ outlineWidth: { type: "number" },
3600
+ outlineColor: { type: "string" },
3601
+ outlineShadow: { type: "boolean" },
3602
+ expectedRevision: { type: "number" },
3603
+ },
3604
+ required: ["mode"],
3605
+ },
3606
+ command: "project.set-speaker-background",
3607
+ },
3579
3608
  {
3580
3609
  name: "project_add_background_effect",
3581
3610
  description:
@@ -4475,7 +4504,7 @@ const TOOLS = [
4475
4504
  {
4476
4505
  name: "project_center_camera_on_face",
4477
4506
  description:
4478
- "Keep the presenter's face centred in the camera card by tracking it (can take a minute). On a podcast clip it tracks every participant's camera so their tiles (pair, grid, screen-share strip) centre on each face (the editor also does this in the background when a podcast opens; a set-podcast-source-crop framing wins). 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).",
4507
+ "Keep the presenter's face centred in a SEPARATE camera card by tracking it (can take a minute). Not for the main video: face-tracked zooms on the main video are project_auto_reframe. On a podcast clip it tracks every participant's camera so their tiles (pair, grid, screen-share strip) centre on each face (the editor also does this in the background when a podcast opens; a set-podcast-source-crop framing wins). 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).",
4479
4508
  inputSchema: {
4480
4509
  type: "object",
4481
4510
  properties: {
@@ -4714,7 +4743,7 @@ const TOOLS = [
4714
4743
  {
4715
4744
  name: "project_auto_reframe",
4716
4745
  description:
4717
- "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).",
4746
+ "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).",
4718
4747
  inputSchema: {
4719
4748
  type: "object",
4720
4749
  properties: {
@@ -5964,6 +5993,30 @@ const TOOLS = [
5964
5993
  },
5965
5994
  command: "caption.set-style",
5966
5995
  },
5996
+ {
5997
+ name: "caption_set_look",
5998
+ description:
5999
+ "Set a COMPLETE caption look in one call, replacing the current one: on/off, template, words per line, position and every override (in style). What the look leaves out goes back to the template's own, never what an earlier style left. How a recipe's captions are applied; use it to copy a look exactly. For one tweak use caption_set_style.",
6000
+ inputSchema: {
6001
+ type: "object",
6002
+ properties: {
6003
+ id: { type: "string" },
6004
+ path: { type: "string" },
6005
+ enabled: { type: "boolean" },
6006
+ templateId: { type: "string", description: "A caption_set_template id." },
6007
+ wordsPerLine: { type: "number", description: "1-10. Default the template's own." },
6008
+ positionY: { type: "number", description: "% from the top. Default the project's." },
6009
+ style: {
6010
+ type: "object",
6011
+ description:
6012
+ "caption_set_style's look fields: fontFamily, fontSize, color, backgroundColor, highlightColor, highlightBackgroundColor, strokeColor, strokeWidth, uppercase, highlightMode, emphasisColor, emphasisBackgroundColor, emphasisScale, emphasisItalic, emphasisFontFamily, boxRotation.",
6013
+ },
6014
+ expectedRevision: { type: "number" },
6015
+ },
6016
+ required: ["enabled"],
6017
+ },
6018
+ command: "caption.set-look",
6019
+ },
5967
6020
  {
5968
6021
  name: "caption_mark_emphasis",
5969
6022
  description:
@@ -6157,17 +6210,20 @@ const TOOLS = [
6157
6210
  properties: {
6158
6211
  script: {
6159
6212
  type: "string",
6160
- description: "What the user will read, plain text (no markdown or stage directions), up to 20,000 characters.",
6213
+ description:
6214
+ "What the user will read, plain text (no markdown or stage directions), up to 20,000 characters.",
6161
6215
  },
6162
6216
  mode: {
6163
6217
  type: "string",
6164
6218
  enum: ["camera", "screen", "both"],
6165
- description: "camera (solo; default with a script), screen (screen only), or both (screen + camera bubble; default without a script).",
6219
+ description:
6220
+ "camera (solo; default with a script), screen (screen only), or both (screen + camera bubble; default without a script).",
6166
6221
  },
6167
6222
  orientation: {
6168
6223
  type: "string",
6169
6224
  enum: ["landscape", "portrait"],
6170
- description: "landscape 16:9 (YouTube) or portrait 9:16 (Shorts, Reels, TikTok): the camera shape for a camera take; a screen take's final shape is set while editing.",
6225
+ description:
6226
+ "landscape 16:9 (YouTube) or portrait 9:16 (Shorts, Reels, TikTok): the camera shape for a camera take; a screen take's final shape is set while editing.",
6171
6227
  },
6172
6228
  speed: { type: "number", description: "Teleprompter scroll speed 0.25-3." },
6173
6229
  fontSize: { type: "number", description: "Teleprompter text size in px, 16-72." },
@@ -7439,7 +7495,7 @@ const TOOLS = [
7439
7495
  {
7440
7496
  name: "recipe_apply_style",
7441
7497
  description:
7442
- "Apply a recipe's fixed style to a project, before recipe_render's content work. Returns { applied, failed, pending, notes }: applied / failed are read back from the project, notes = parts left to you. Fails, changing nothing, when the recipe needs footage the project lacks (a camera recipe on an audio-only project).",
7498
+ "Apply a recipe's fixed style to a project, before recipe_render's content work: aspect, the full caption look, speaker background, vertical framing, grade, camera layout. It REPLACES an earlier recipe's look. Returns { applied, failed, pending, notes }: applied / failed are read back from the project, notes = parts left to you. Fails, changing nothing, when the recipe needs footage the project lacks (a camera recipe on an audio-only project).",
7443
7499
  inputSchema: {
7444
7500
  type: "object",
7445
7501
  properties: {
@@ -7452,6 +7508,17 @@ const TOOLS = [
7452
7508
  },
7453
7509
  command: "recipe.apply-style",
7454
7510
  },
7511
+ {
7512
+ name: "recipe_look_frames",
7513
+ description:
7514
+ "The recipe's target look as images (attached): stills of its finished edit at key moments, each with what it shows. Look before editing with a recipe; compare project_render_frame at the matching beats before saying done. Match the look, never the content.",
7515
+ inputSchema: {
7516
+ type: "object",
7517
+ properties: { id: { type: "string", description: "Recipe id." } },
7518
+ required: ["id"],
7519
+ },
7520
+ command: "recipe.look-frames",
7521
+ },
7455
7522
  {
7456
7523
  name: "recipe_save",
7457
7524
  description:
@@ -7462,7 +7529,7 @@ const TOOLS = [
7462
7529
  recipe: {
7463
7530
  type: "object",
7464
7531
  description:
7465
- "{ id?, title, description, category, footage?, aspectRatio?, fields: [{ key, label, type: text|longtext|select|color|images, default?, options?, required?, help?, minCount?, maxCount?, optional? }], prompt, style?: { aspectRatio?, colorCorrection?, lut?: { preset, intensity? }, background?: { mode, image? }, cameraLayout?, captions?: { enabled, templateId? }, wallpaper? }, checklist: string[] }",
7532
+ "{ id?, title, description, category, footage?, aspectRatio?, fields: [{ key, label, type: text|longtext|select|color|images, default?, options?, required?, help?, minCount?, maxCount?, optional? }], prompt, lookFrames?: [{ src: absolute image path, shows: what it shows (position, size, colours), atMs? }] (up to 8 stills of the target look; copied into the recipe), style?: { aspectRatio?, colorCorrection?, lut?: { preset, intensity? }, speakerBackground?: { mode: none|blur|remove|image, image?, strength?, outline?: { enabled, width?, color?, shadow? } }, framing?: { shortsLayout: full|camera-corner }, cameraLayout?, captions?: { enabled, templateId?, wordsPerLine?, positionY?, style?: caption_set_look's style }, broll?: { use: none|ask|generate|user-images, layout?: full|split|card, notes? }, graphics?: { family?, titleFont?, bodyFont?, accentColor?, textColor?, surfaceColor?, notes? }, wallpaper? }, checklist: string[] }. Put the WHOLE look in style (every caption value, the background, framing, B-roll rule, title fonts and colours): it is applied exactly, the prompt only covers what depends on the footage.",
7466
7533
  },
7467
7534
  },
7468
7535
  required: ["recipe"],
@@ -9049,10 +9116,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
9049
9116
  for (const previewPath of previews) {
9050
9117
  const fs = await import("node:fs/promises");
9051
9118
  const buf = await fs.readFile(previewPath);
9119
+ const ext = previewPath.toLowerCase().split(".").pop();
9052
9120
  content.push({
9053
9121
  type: "image",
9054
9122
  data: buf.toString("base64"),
9055
- mimeType: "image/png",
9123
+ mimeType:
9124
+ ext === "jpg" || ext === "jpeg"
9125
+ ? "image/jpeg"
9126
+ : ext === "webp"
9127
+ ? "image/webp"
9128
+ : "image/png",
9056
9129
  });
9057
9130
  }
9058
9131
  } catch (imgErr) {
@@ -9113,6 +9186,12 @@ function collectInlinePreviewPaths(command, data) {
9113
9186
  if (command === "motion.screenshot") {
9114
9187
  return typeof data.previewPath === "string" ? [data.previewPath] : [];
9115
9188
  }
9189
+ if (command === "recipe.look-frames") {
9190
+ const frames = Array.isArray(data.frames) ? data.frames : [];
9191
+ return frames
9192
+ .map((f) => (f && typeof f.path === "string" ? f.path : null))
9193
+ .filter((p) => p !== null);
9194
+ }
9116
9195
  if (command === "motion.verify-frames") {
9117
9196
  const frames = Array.isArray(data.frames) ? data.frames : [];
9118
9197
  return frames
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@writepanda/mcp",
3
- "version": "1.265.0",
3
+ "version": "1.269.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",