@writepanda/mcp 1.259.0 → 1.265.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 CHANGED
@@ -35,7 +35,13 @@ import path from "node:path";
35
35
  import process from "node:process";
36
36
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
37
37
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
38
- import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
38
+ import {
39
+ CallToolRequestSchema,
40
+ ListResourcesRequestSchema,
41
+ ListResourceTemplatesRequestSchema,
42
+ ListToolsRequestSchema,
43
+ ReadResourceRequestSchema,
44
+ } from "@modelcontextprotocol/sdk/types.js";
39
45
  import {
40
46
  appLaunchEnv,
41
47
  callWithReconnect,
@@ -47,6 +53,14 @@ import {
47
53
  IN_APP_UNAVAILABLE,
48
54
  isInAppCaller,
49
55
  } from "./appConnection.mjs";
56
+ import {
57
+ APP_TOOLS,
58
+ clientSupportsUi,
59
+ listUiResources,
60
+ MediaRegistry,
61
+ readUiResource,
62
+ toolUiMeta,
63
+ } from "./apps/index.mjs";
50
64
  import { portableSchema } from "./portableSchema.mjs";
51
65
 
52
66
  // ── Credentials + transport ───────────────────────────────────────────
@@ -569,13 +583,25 @@ const MOTION_ELEMENT_TYPES = [
569
583
  "ticker",
570
584
  "credits",
571
585
  "crawl",
586
+ "reelWall",
587
+ "cardFan",
588
+ "orbit",
589
+ "polaroids",
590
+ "wordWall",
591
+ "neon",
592
+ "bloom",
593
+ "scrapbook",
594
+ "keycaps",
595
+ "productCard",
596
+ "scrollList",
597
+ "splitScreen",
572
598
  ];
573
599
  const MOTION_ELEMENT_PROPS = {
574
600
  type: { type: "string", enum: MOTION_ELEMENT_TYPES },
575
601
  content: {
576
602
  type: ["object", "string", "array"],
577
603
  description:
578
- 'keyword/chip/stamp/slam/behind/highlight { text } (or a bare string); count { value, to?, prefix?, suffix?, label?, decimals? } (value "20-30" = range); steps { items[], cues? (ms offsets from start) }; lowerThird { title, subtitle? (<=320 chars, wraps), look?: plate|plain|typewriter, typeSpeed? (typewriter pace, 5-80 chars/s, default ~22) }; endCard { lines: [a, b] }; progress { label?, fromMs?, toMs? }; iconPop { emoji } | { icon }; frame { look: viewfinder | selection | corners, label? }; sticker { image: transparent PNG path, label? }; ticker { text (one story per line, <=2000 chars), label?, speed? (40-600 px/s) }; credits { title?, entries: [{ role?, name }] | "Role: Name" strings (<=200) }; crawl { title?, text (<=6000 chars), tilt? (0-80), turn? (-60..60) }. Any script, symbols and emoji draw.',
604
+ 'keyword/chip/stamp/slam/behind/highlight { text } (or a bare string); count { value, to?, prefix?, suffix?, label?, decimals? } (value "20-30" = range); steps { items[], cues? (ms offsets from start) }; lowerThird { title, subtitle? (<=320 chars, wraps), look?: plate|plain|typewriter, typeSpeed? (typewriter pace, 5-80 chars/s, default ~22) }; endCard { lines: [a, b] }; progress { label?, fromMs?, toMs? }; iconPop { emoji } | { icon }; frame { look: viewfinder | selection | corners, label? }; sticker { image: transparent PNG path or "@speaker" (the speaker cut out of her shot), label?, twin? (mirrored pair, one each side) }; ticker { text (one story per line, <=2000 chars), label?, speed? (40-600 px/s) }; credits { title?, entries: [{ role?, name }] | "Role: Name" strings (<=200) }; crawl { title?, text (<=6000 chars), tilt? (0-80), turn? (-60..60) }. Creator effects (picture slots take a path or "@speaker" = the speaker\'s own live shot, the default): reelWall { images?, count? 4-24 } (tiles of her shot behind her); cardFan { images?, count? 3-9 } (card reveal: cards fan in an arc behind her head); orbit { images?, count? 2-6 } (cards circle her, layer around); polaroids { images?, count? 2-8, captions? } (scattered around her, layer around); wordWall { text, rows? 3-14, background? } (word repeated in rows behind her); neon { text, flicker? }; bloom { text } (colour bloom); scrapbook { text } (ransom-note letters); keycaps { text <=24 } (keys falling, pile at the bottom); productCard { title, subtitle?, price?, image?, button? ("Add to cart"), done? ("Added") }; scrollList { title?, items: (string | { title, subtitle?, image? })[] <=30, look?: playlist|feed } (phone list scrolling); splitScreen { panels? 2|3, images?, zooms? 1-4 } (three scenes). Any script, symbols and emoji draw.',
579
605
  },
580
606
  atMs: { type: "number", description: "Start, edited ms. Or wordId." },
581
607
  wordId: {
@@ -619,7 +645,12 @@ const MOTION_ELEMENT_PROPS = {
619
645
  description:
620
646
  "auto (clear of the face) | top | upper | center | lower | bottom | { x, y, w? } (% of frame; x,y = centre).",
621
647
  },
622
- layer: { type: "string", enum: ["front", "behind"] },
648
+ layer: {
649
+ type: "string",
650
+ enum: ["front", "behind", "around"],
651
+ description:
652
+ "front | behind (behind the speaker's cut-out) | around (orbit, polaroids: the far side behind her, the near side in front; their default). Default per type.",
653
+ },
623
654
  sound: {
624
655
  type: "string",
625
656
  enum: ["auto", "none", "pop", "whoosh", "hit", "tick", "bell"],
@@ -1705,6 +1736,19 @@ const TOOLS = [
1705
1736
  },
1706
1737
  command: "project.show",
1707
1738
  },
1739
+ {
1740
+ name: "project_edit_summary",
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.",
1743
+ inputSchema: {
1744
+ type: "object",
1745
+ properties: {
1746
+ id: { type: "string" },
1747
+ path: { type: "string" },
1748
+ },
1749
+ },
1750
+ command: "project.edit-summary",
1751
+ },
1708
1752
  {
1709
1753
  name: "matte_status",
1710
1754
  description:
@@ -3045,7 +3089,10 @@ const TOOLS = [
3045
3089
  type: "boolean",
3046
3090
  description: "Follow the recorded cursor (screen recordings).",
3047
3091
  },
3048
- soundUrl: { type: "string", description: 'Default bundled:sound/swoosh-fast; "none".' },
3092
+ soundUrl: {
3093
+ type: "string",
3094
+ description: 'Default bundled:sound/motion-swoosh-up; "none".',
3095
+ },
3049
3096
  soundVolume: { type: "number", description: "0-1." },
3050
3097
  anchorSourceMs: ANCHOR_PROP,
3051
3098
  anchorSourceEndMs: ANCHOR_END_PROP,
@@ -4711,7 +4758,8 @@ const TOOLS = [
4711
4758
  },
4712
4759
  frameRate: {
4713
4760
  type: ["number", "string"],
4714
- description: "auto (default) | 30 | 60 | source (fastest source).",
4761
+ description:
4762
+ "auto (default) | 23.976 | 24 | 25 | 29.97 | 30 | 50 | 59.94 | 60 (25 / 50 for PAL) | source (fastest source).",
4715
4763
  },
4716
4764
  expectedRevision: { type: "number" },
4717
4765
  },
@@ -6100,6 +6148,33 @@ const TOOLS = [
6100
6148
  inputSchema: { type: "object", properties: {} },
6101
6149
  command: "recording.list-sources",
6102
6150
  },
6151
+ {
6152
+ name: "recording_open_recorder",
6153
+ description:
6154
+ "Open PandaStudio's recorder for the USER to record themselves, with the script you wrote loaded in the teleprompter (scrolls with the take, voice tracking available, hidden from the recording). Use after writing a script the user will read on camera; then tell them to press Record in PandaStudio's recorder bar. You never start their camera or microphone. When they stop, the take opens in the editor as a new project (project_current). Returns { opened, mode, teleprompter: { words, readSeconds } | false }. For an unattended screen capture use recording_start.",
6155
+ inputSchema: {
6156
+ type: "object",
6157
+ properties: {
6158
+ script: {
6159
+ type: "string",
6160
+ description: "What the user will read, plain text (no markdown or stage directions), up to 20,000 characters.",
6161
+ },
6162
+ mode: {
6163
+ type: "string",
6164
+ enum: ["camera", "screen", "both"],
6165
+ description: "camera (solo; default with a script), screen (screen only), or both (screen + camera bubble; default without a script).",
6166
+ },
6167
+ orientation: {
6168
+ type: "string",
6169
+ 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.",
6171
+ },
6172
+ speed: { type: "number", description: "Teleprompter scroll speed 0.25-3." },
6173
+ fontSize: { type: "number", description: "Teleprompter text size in px, 16-72." },
6174
+ },
6175
+ },
6176
+ command: "recording.open-recorder",
6177
+ },
6103
6178
  {
6104
6179
  name: "recording_start",
6105
6180
  description:
@@ -6315,7 +6390,7 @@ const TOOLS = [
6315
6390
  {
6316
6391
  name: "project_compose_soundtrack",
6317
6392
  description:
6318
- "Score this edit: music + sound design composed FROM THE PROJECT'S OWN TIMELINE (zooms, graphics, pops, cuts, speech, stressed words) and placed under the voice with ducking; takes over zoom/overlay default sounds; replaces the previous score on re-run. Call LAST, after cuts/zooms/graphics. dryRun returns the score for media_compose_soundtrack. Returns { audioPath, scorePath, events, heroMoments, acts, soundsTakenOver, replaced }.",
6393
+ "Score this edit: music composed FROM THE PROJECT'S OWN TIMELINE (zooms, graphics, pops, cuts, speech, stressed words) placed under the voice with ducking, plus sound effects as recorded library cues (group score-sfx, sparse under a voice); takes over zoom/overlay/graphic sounds; replaces the previous score and its cues on re-run. Call LAST, after cuts/zooms/graphics, and again after changing them (project_read composedScore.stale / doubledSounds). dryRun returns the score and sfxCues. Returns { audioPath, scorePath, events, heroMoments, acts, soundsTakenOver, sfxCues, paperSfx, replaced }.",
6319
6394
  inputSchema: {
6320
6395
  type: "object",
6321
6396
  properties: {
@@ -6327,7 +6402,8 @@ const TOOLS = [
6327
6402
  sfx: { type: "boolean", description: "Default true." },
6328
6403
  useSpeech: {
6329
6404
  type: "boolean",
6330
- description: "Hero hits on the most stressed words. Default true.",
6405
+ description:
6406
+ "Hits on the most stressed words even with nothing on screen. Default true for promo only.",
6331
6407
  },
6332
6408
  cues: {
6333
6409
  type: "array",
@@ -6354,7 +6430,7 @@ const TOOLS = [
6354
6430
  {
6355
6431
  name: "project_add_motion_element",
6356
6432
  description:
6357
- "Native motion element: an editable, word-timed graphic the engine draws (preview = export, no HTML render): keyword headline, chip, stamp, count (number/range rolls up), steps, slam (+ camera kick), behind (word behind the speaker), lowerThird, highlight, endCard, progress, iconPop, frame (viewfinder / selection box / corners around the speaker). Anchor with wordId (follows the word through cuts) or atMs. Brand kit by default. Returns { elementId, element }.",
6433
+ 'Native motion element: an editable, word-timed graphic the engine draws (preview = export, no HTML render): keyword headline, chip, stamp, count (number/range rolls up), steps, slam (+ camera kick), behind (word behind the speaker), lowerThird, highlight, endCard, progress, iconPop, frame (viewfinder / selection box / corners around the speaker), sticker, ticker, credits, crawl, and the short-form creator effects around the speaker: reelWall, cardFan (card reveal), orbit, polaroids, wordWall, neon, bloom (colour bloom), scrapbook, keycaps (keys falling), productCard (add to cart), scrollList (scroll overlay), splitScreen (three scenes); "@speaker" in any picture slot shows her own live shot. Anchor with wordId (follows the word through cuts) or atMs. Brand kit by default. Returns { elementId, element }.',
6358
6434
  inputSchema: {
6359
6435
  type: "object",
6360
6436
  properties: {
@@ -7170,16 +7246,165 @@ const TOOLS = [
7170
7246
  },
7171
7247
  command: "recipe.prepare-reference",
7172
7248
  },
7249
+ {
7250
+ name: "reference_check_link",
7251
+ description:
7252
+ "Check a pasted reference link without downloading: YouTube, TikTok, Instagram and X video links are accepted. Returns { url, platform } or a clear error (not a supported site, not a single video, local/private address).",
7253
+ inputSchema: {
7254
+ type: "object",
7255
+ properties: { url: { type: "string" } },
7256
+ required: ["url"],
7257
+ },
7258
+ command: "reference.check-link",
7259
+ },
7260
+ {
7261
+ name: "reference_gallery",
7262
+ description:
7263
+ "Curated reference videos (Home → Start from a proven edit → Don't see your style?): real creators' videos worth copying the edit of, from the signed gallery on writepanda.ai. Returns { entries: [{ id, title, creator (@handle), creatorName, avatar, platform, format, views, viewsLabel, durationMs, poster, sourceUrl (the original post), referenceVideo (hover preview, optional), resultVideo, recipeId, tags }] }. Prepare one with reference_prepare { galleryId } (fetches the whole original from sourceUrl on this computer); recipeId = a prebuilt recipe for its style.",
7264
+ inputSchema: { type: "object", properties: {} },
7265
+ command: "reference.gallery",
7266
+ },
7267
+ {
7268
+ name: "reference_prepare",
7269
+ description:
7270
+ "'Make my video like this one': bring in a reference video and study it. Exactly one of url (YouTube / TikTok / Instagram / X video link, fetched on this computer), file (absolute .mp4/.mov/.webm) or galleryId (reference_gallery). Up to 30 minutes. Async: returns { jobId }; job_wait gives { id, title, durationMs, manifestPath (overview + motion sheets, audio excerpts), source }. Clear errors for private / sign-in-only videos and unsupported sites. From then on the reference is GUARDED: every media verb and export refuses it (code REFERENCE_MEDIA). Next: reference_analyze, then reference_render.",
7271
+ inputSchema: {
7272
+ type: "object",
7273
+ properties: {
7274
+ url: { type: "string", description: "YouTube / TikTok / Instagram / X video link." },
7275
+ file: { type: "string", description: "Absolute path of an .mp4 / .mov / .webm." },
7276
+ galleryId: { type: "string", description: "Curated reference id (reference_gallery)." },
7277
+ title: { type: "string" },
7278
+ },
7279
+ },
7280
+ command: "reference.prepare",
7281
+ },
7282
+ {
7283
+ name: "reference_analyze",
7284
+ description:
7285
+ "The setup-screen analysis of a prepared reference: title, format, aspect, summary, the rebuild list (elements: every graphic / transition / caption / effect / camera move with its reference time) and up to 5 slots for the user's material (footage first; logo, screen, product, text). Without `analysis` it runs on the user's own assistant model with the contact sheets attached (async { jobId }; default slots + analysisError when no model). If you looked at the sheets yourself, pass your own `analysis` (same shape): stored at once.",
7286
+ inputSchema: {
7287
+ type: "object",
7288
+ properties: {
7289
+ id: { type: "string", description: "Reference id." },
7290
+ analysis: {
7291
+ type: "object",
7292
+ description:
7293
+ "{ title, format: short|long, aspect: 9:16|16:9|1:1|3:4, summary, elements: [{ name, kind: graphic|transition|caption|effect|camera|sound, atMs, description }], slots: [{ key, kind: footage|logo|screen|product|text, label, hint, required }] }",
7294
+ },
7295
+ providerId: { type: "string" },
7296
+ modelId: { type: "string" },
7297
+ },
7298
+ required: ["id"],
7299
+ },
7300
+ command: "reference.analyze",
7301
+ },
7302
+ {
7303
+ name: "reference_render",
7304
+ description:
7305
+ "The one instruction that makes the user's video in the reference's style, from the filled slots: mode learn (learn the style from the evidence; saveRecipe keeps it as a personal recipe, default true) or recipe (apply recipeId, default the curated entry's prebuilt recipe), then rebuild every element natively, project_render_frame → reference_compare → reference_score each one BEFORE the user's content goes in. mode save: only study the reference and save its style as a personal recipe named recipeTitle, no video now (slots ignored). Returns { prompt, missing } (required slots still empty; always empty for save). Follow the prompt. Changes nothing itself.",
7306
+ inputSchema: {
7307
+ type: "object",
7308
+ properties: {
7309
+ id: { type: "string", description: "Reference id." },
7310
+ slots: {
7311
+ type: "object",
7312
+ description:
7313
+ '{ "<slot key>": { "mode": "file", "path": "/abs" } | { "mode": "files", "paths": ["/abs"] } | { "mode": "text", "text": "..." } | { "mode": "agent" } }',
7314
+ },
7315
+ mode: { type: "string", enum: ["learn", "recipe", "save"] },
7316
+ recipeTitle: { type: "string", description: "mode save: the new recipe's name." },
7317
+ recipeId: { type: "string" },
7318
+ saveRecipe: { type: "boolean" },
7319
+ projectId: { type: "string", description: "Edit this project instead of a new one." },
7320
+ note: { type: "string", description: "The user's own words." },
7321
+ },
7322
+ required: ["id"],
7323
+ },
7324
+ command: "reference.render",
7325
+ },
7326
+ {
7327
+ name: "reference_compare",
7328
+ description:
7329
+ "Side-by-side JPG of the reference at atMs (reference time, left) and a rendered frame of the user's edit (the PNG from project_render_frame, right), to judge one rebuilt graphic / transition / effect. Returns { path, referenceFrame }. Look at it, then reference_score.",
7330
+ inputSchema: {
7331
+ type: "object",
7332
+ properties: {
7333
+ id: { type: "string" },
7334
+ frame: { type: "string", description: "Absolute path of the rendered frame." },
7335
+ atMs: { type: "number", description: "Reference time (ms)." },
7336
+ },
7337
+ required: ["id", "frame", "atMs"],
7338
+ },
7339
+ command: "reference.compare",
7340
+ },
7341
+ {
7342
+ name: "reference_score",
7343
+ description:
7344
+ "Record how close a rebuilt element is to the reference after reference_compare: score 1-5 (4 = close enough to ship, 5 = indistinguishable in look, motion and timing) and a note of what still differs. One score per element name.",
7345
+ inputSchema: {
7346
+ type: "object",
7347
+ properties: {
7348
+ id: { type: "string" },
7349
+ element: { type: "string" },
7350
+ score: { type: "number", minimum: 1, maximum: 5 },
7351
+ note: { type: "string" },
7352
+ atMs: { type: "number" },
7353
+ },
7354
+ required: ["id", "element", "score"],
7355
+ },
7356
+ command: "reference.score",
7357
+ },
7358
+ {
7359
+ name: "reference_attach",
7360
+ description:
7361
+ "Record on a project which reference it follows (and the recipe), so later turns can pick the work up; project_read shows it as `reference`. clear=true removes it. Metadata only: the reference's media never enters the project.",
7362
+ inputSchema: {
7363
+ type: "object",
7364
+ properties: {
7365
+ id: { type: "string", description: "Reference id (not needed with clear)." },
7366
+ projectId: { type: "string" },
7367
+ recipeId: { type: "string" },
7368
+ clear: { type: "boolean" },
7369
+ },
7370
+ required: ["projectId"],
7371
+ },
7372
+ command: "reference.attach",
7373
+ },
7374
+ {
7375
+ name: "reference_get",
7376
+ description:
7377
+ "One prepared reference: { id, title, source, durationMs, manifestPath, analysis, scores, projectIds }.",
7378
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
7379
+ command: "reference.get",
7380
+ },
7381
+ {
7382
+ name: "reference_list",
7383
+ description: "Prepared references, newest first.",
7384
+ inputSchema: { type: "object", properties: {} },
7385
+ command: "reference.list",
7386
+ },
7387
+ {
7388
+ name: "reference_delete",
7389
+ description:
7390
+ "Delete a prepared reference (its downloaded video, study sheets, side-by-sides). An uploaded file stays where the user keeps it; projects made from it are untouched.",
7391
+ inputSchema: { type: "object", properties: { id: { type: "string" } }, required: ["id"] },
7392
+ command: "reference.delete",
7393
+ },
7173
7394
  {
7174
7395
  name: "recipe_pick",
7175
7396
  description:
7176
- "No style named ('edit my video', 'make this a Short')? The recipes that fit this project's format and footage, each with pickWhen, plus defaultId. Choose by what's said, then recipe_apply_style + recipe_render.",
7397
+ "No style named ('edit my video', 'make this a Short')? The recipes that fit this project's format and footage, each with pickWhen, plus defaultId. Choose by what's said (defaultId when nothing clearly fits, never a plain clean-up), then recipe_apply_style + recipe_render. A Short cut from a landscape long-form video (a project_fork_from_shot fork, a landscape source, or format short on a 16:9 project) comes back fromLongForm with its own default. On a 16:9 project the user wants as a Short, pass format short.",
7177
7398
  inputSchema: {
7178
7399
  type: "object",
7179
7400
  properties: {
7180
7401
  id: { type: "string", description: "Project id (default: the open project)." },
7181
7402
  path: { type: "string" },
7182
- format: { type: "string", enum: ["short", "long"] },
7403
+ format: {
7404
+ type: "string",
7405
+ enum: ["short", "long"],
7406
+ description: "Default from the project's aspect ratio (9:16 = short).",
7407
+ },
7183
7408
  footage: {
7184
7409
  type: "string",
7185
7410
  enum: ["camera", "screen", "podcast", "audio", "none"],
@@ -7810,7 +8035,8 @@ const TOOLS = [
7810
8035
  },
7811
8036
  frameRate: {
7812
8037
  type: ["number", "string"],
7813
- description: "30 | 60 | source | auto; omit = project setting.",
8038
+ description:
8039
+ "23.976 | 24 | 25 | 29.97 | 30 | 50 | 59.94 | 60 | source | auto; omit = project setting.",
7814
8040
  },
7815
8041
  format: {
7816
8042
  type: "string",
@@ -7865,6 +8091,24 @@ const TOOLS = [
7865
8091
  },
7866
8092
  command: "export.verify",
7867
8093
  },
8094
+ {
8095
+ name: "export_timeline",
8096
+ description:
8097
+ "Export the edit as an editable timeline for Premiere Pro / DaVinci Resolve / Final Cut: an FCP 7 XML on the original media (every cut, speed as Time Remap, camera on V2, overlays above, voice + music tracks) plus an .srt of the kept words. Zooms, caption styling, motion graphics, layouts and grade are not in it: tell the user the returned notes. Sync: { xmlPath, srtPath, fps, durationMs, cuts, missingFiles, notes }.",
8098
+ inputSchema: {
8099
+ type: "object",
8100
+ properties: {
8101
+ id: { type: "string" },
8102
+ path: { type: "string" },
8103
+ outputPath: {
8104
+ type: "string",
8105
+ description: "A .xml path or a folder; default the recordings folder.",
8106
+ },
8107
+ captions: { type: "boolean", description: "Write the .srt beside it (default true)." },
8108
+ },
8109
+ },
8110
+ command: "export.timeline",
8111
+ },
7868
8112
  {
7869
8113
  name: "export_list",
7870
8114
  description:
@@ -8001,6 +8245,24 @@ const TOOLS = [
8001
8245
  },
8002
8246
  command: "clips.delete",
8003
8247
  },
8248
+ {
8249
+ name: "clips_remove_clip",
8250
+ description:
8251
+ "Remove ONE clip from a clip set (the Clips screen's Remove on a clip card). Its stills go and its moment is remembered so clips.retry won't make it again; its project moves to the Trash (recoverable) unless keepProject is true. Exported files stay in My Exports. A clip still being made is dropped by the running job. Returns { clipSetId, removed, projectDeleted, clipsLeft }.",
8252
+ inputSchema: {
8253
+ type: "object",
8254
+ properties: {
8255
+ id: { type: "string", description: "Clip set id." },
8256
+ clipId: { type: "string", description: "The clip's id (from clips_get)." },
8257
+ keepProject: {
8258
+ type: "boolean",
8259
+ description: "Keep the clip's project in Projects instead of the Trash. Default false.",
8260
+ },
8261
+ },
8262
+ required: ["id", "clipId"],
8263
+ },
8264
+ command: "clips.remove-clip",
8265
+ },
8004
8266
  {
8005
8267
  name: "ideas_research",
8006
8268
  description:
@@ -8654,8 +8916,9 @@ Before an editing pass, read the skill's "Which tool for which moment" section (
8654
8916
  3. Fix misheard names / brands with transcript_find_replace (transcript_insert_words for dropped words).
8655
8917
  4. transcript_find_issues, then transcript_delete_words on each duplicate take / false start (keep the LAST take; keep severity "low" ones).
8656
8918
  5. transcript_remove_silences.
8919
+ 5b. A Short from a landscape video (a long-form recording, the project isn't 9:16): cut the Short out now. export_generate_shots { projectId }, then project_fork_from_shot on the best shot (offer the top shots when the user is there) and run steps 6-9 on the fork. Only a clip already under about 90 s goes vertical in place (project_set_aspect_ratio 9:16).
8657
8920
  6. audio_enhance (Enhance voice) on clips not yet enhanced with its defaults (Light, plus Studio mic sound on Apple silicon; don't pass strength or restore unless the user asked); keepBackground true for vlogs / ambience; no echo (Enhance voice already removes room echo with the Studio AI model; only use echo when the result reports the model did not run (fallback)).
8658
- 7. The style, after steps 2-5 (they shift the timeline). The user's named style, recipe, creator or a saved memory wins. Otherwise call recipe_pick: it returns the recipes that fit this project's format and footage, each with pickWhen, plus defaultId. Read the transcript, choose the ONE candidate whose pickWhen matches what is said (defaultId when none clearly fits), do its "before" step if any, then recipe_apply_style + recipe_render and follow the prompt: the recipe decides the style (captions, pacing, kinds of moves, music, transitions). Graphics inside it are designed by you: a named template is the reference look; author your own (motion_render_html, same palette and type) when a beat needs something the template can't show. (An app without recipe_pick, 2.0.5 and older: recipe_list with format short or long, and choose by description and footage.) Tell the user which recipe and why in one line and name one or two alternatives. Only when no candidate exists or the user asked for a plain clean-up: captions (caption_toggle, then caption_set_template: hormoziEmphasis for vertical, classic for long-form; not glowStack unless asked), a few motion graphics, emphasis zooms.
8921
+ 7. The style, after steps 2-5 (they shift the timeline). The user's named style, recipe, creator or a saved memory wins. Otherwise call recipe_pick: it returns the recipes that fit this project's format and footage, each with pickWhen, plus defaultId. Read the transcript, choose the ONE candidate whose pickWhen matches what is said (defaultId when none clearly fits; a Short cut from a long-form video comes back fromLongForm and its defaultId is made for that), do its "before" step if any, then recipe_apply_style + recipe_render and follow the prompt: the recipe decides the style (captions, pacing, kinds of moves, music, transitions). Graphics inside it are designed by you: a named template is the reference look; author your own (motion_render_html, same palette and type) when a beat needs something the template can't show. (An app without recipe_pick, 2.0.5 and older: recipe_list with format short or long, and choose by description and footage.) Tell the user which recipe and why in one line and name one or two alternatives. Never trade the default for a plain edit because no candidate fits perfectly. Only when no candidate exists or the user asked for a plain clean-up: captions (caption_toggle, then caption_set_template: hormoziEmphasis for vertical, classic for long-form; not glowStack unless asked), a few motion graphics, emphasis zooms.
8659
8922
  8. B-roll, last: pick the moments yourself from what's said (any language), check them with project_check_broll, show the user your moments and ASK: their own images, generated on their image connector (only if one is connected; it uses their credits), or no B-roll. Never generate or invent images without their answer. Place what they give with project_add_broll. Nobody to ask: skip it and say so.
8660
8923
  9. llm_generate_title / description / timestamps, then preview_show and ask before export_start. Never export before the user has seen the preview.
8661
8924
  Report the real count each step returned. No music, intro/outro cards or FX overlays unless the recipe or the user asks for them.
@@ -8669,6 +8932,8 @@ Publishing has daily limits (YouTube uploads and updates per license, social pos
8669
8932
 
8670
8933
  PandaStudio problems (a capability gap like an imported font the export can't draw, a command failing unexpectedly, a render not matching the project): call system_report_issue yourself, then tell the user you reported it. Not for user mistakes, missing keys or taste.
8671
8934
 
8935
+ Views: in chat apps that show MCP views, preview_show, project_render_frame / _render_sheet and project_edit_summary also show the frames and what the edit contains, and clips_get / clips_make show the clips as cards the user can open, export or remove. Summarise in a line instead of describing each frame or clip; what the user does in a view arrives as context, don't redo it.
8936
+
8672
8937
  Review items: the user can pin notes and drawings on the preview and send them in one turn (listed in the editor context and the message, crops attached in item order). Apply each one, then review_resolve with its id. review_list shows the open ones with a crop image path.
8673
8938
 
8674
8939
  Destination decides everything: infer it (Shorts/TikTok/vertical/<=90s: shorts; YouTube/tutorial/>8 min: youtube-long; LinkedIn; Loom/internal), else ask once.
@@ -8683,23 +8948,49 @@ const server = new Server(
8683
8948
  {
8684
8949
  capabilities: {
8685
8950
  tools: {},
8951
+ // Only the ui:// views (MCP Apps), listed for clients that render them.
8952
+ resources: {},
8686
8953
  },
8687
8954
  instructions: INSTRUCTIONS,
8688
8955
  },
8689
8956
  );
8690
8957
 
8958
+ /** Whether the connected client renders MCP Apps views (apps/index.mjs). */
8959
+ const uiEnabled = () => clientSupportsUi(server.getClientCapabilities(), { inApp: IN_APP });
8960
+ /** Image paths the app reported this session, served to views by ui_read_media. */
8961
+ const media = new MediaRegistry();
8962
+
8691
8963
  server.setRequestHandler(ListToolsRequestSchema, async () => {
8692
- return {
8693
- tools: TOOLS.map((t) => ({
8964
+ const ui = uiEnabled();
8965
+ const tools = TOOLS.map((t) => {
8966
+ const meta = ui ? toolUiMeta(t.name) : undefined;
8967
+ return {
8694
8968
  name: t.name,
8695
8969
  description: t.description,
8696
8970
  // Gemini rejects multi-type fields that JSON Schema allows.
8697
8971
  inputSchema: portableSchema(t.inputSchema),
8698
- })),
8699
- };
8972
+ ...(meta ? { _meta: meta } : {}),
8973
+ };
8974
+ });
8975
+ return { tools: ui ? [...tools, ...APP_TOOLS] : tools };
8976
+ });
8977
+
8978
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
8979
+ resources: uiEnabled() ? listUiResources() : [],
8980
+ }));
8981
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
8982
+ resourceTemplates: [],
8983
+ }));
8984
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
8985
+ const res = readUiResource(request.params.uri, mcpVersion());
8986
+ if (!res) throw new Error(`unknown resource: ${request.params.uri}`);
8987
+ return res;
8700
8988
  });
8701
8989
 
8702
8990
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
8991
+ if (request.params.name === "ui_read_media") {
8992
+ return media.read(request.params.arguments?.paths);
8993
+ }
8703
8994
  const tool = TOOLS.find((t) => t.name === request.params.name);
8704
8995
  if (!tool) {
8705
8996
  return {
@@ -8753,6 +9044,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
8753
9044
  ];
8754
9045
  try {
8755
9046
  const data = result?.data ?? result;
9047
+ media.note(data);
8756
9048
  const previews = collectInlinePreviewPaths(command, data);
8757
9049
  for (const previewPath of previews) {
8758
9050
  const fs = await import("node:fs/promises");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@writepanda/mcp",
3
- "version": "1.259.0",
3
+ "version": "1.265.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",