@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/apps/clips.mjs +401 -0
- package/bin/apps/editPreview.mjs +389 -0
- package/bin/apps/index.mjs +256 -0
- package/bin/apps/recorder.mjs +189 -0
- package/bin/apps/viewKit.mjs +506 -0
- package/bin/server.mjs +308 -16
- package/package.json +1 -1
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 {
|
|
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: {
|
|
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: {
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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: {
|
|
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:
|
|
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
|
-
|
|
8693
|
-
|
|
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.
|
|
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",
|