@writepanda/mcp 1.262.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/index.mjs +15 -1
- package/bin/apps/recorder.mjs +189 -0
- package/bin/server.mjs +218 -6
- package/package.json +1 -1
package/bin/apps/index.mjs
CHANGED
|
@@ -20,8 +20,9 @@ import { readFile, stat } from "node:fs/promises";
|
|
|
20
20
|
import path from "node:path";
|
|
21
21
|
import { CLIPS_URI, clipsHtml } from "./clips.mjs";
|
|
22
22
|
import { EDIT_PREVIEW_URI, editPreviewHtml } from "./editPreview.mjs";
|
|
23
|
+
import { RECORDER_URI, recorderHtml } from "./recorder.mjs";
|
|
23
24
|
|
|
24
|
-
export { CLIPS_URI, EDIT_PREVIEW_URI };
|
|
25
|
+
export { CLIPS_URI, EDIT_PREVIEW_URI, RECORDER_URI };
|
|
25
26
|
export const UI_EXTENSION = "io.modelcontextprotocol/ui";
|
|
26
27
|
export const UI_MIME = "text/html;profile=mcp-app";
|
|
27
28
|
/** Deprecated flat key some hosts still read; the official helper sets both. */
|
|
@@ -44,6 +45,14 @@ const RESOURCES = [
|
|
|
44
45
|
"A clip set as cards (still, title, hook, score, length) with Open, Export and Remove.",
|
|
45
46
|
html: clipsHtml,
|
|
46
47
|
},
|
|
48
|
+
{
|
|
49
|
+
uri: RECORDER_URI,
|
|
50
|
+
name: "recorder",
|
|
51
|
+
title: "Recorder",
|
|
52
|
+
description:
|
|
53
|
+
"The script loaded into PandaStudio's teleprompter (editable, with reading time), Open the recorder again, and I've recorded it.",
|
|
54
|
+
html: recorderHtml,
|
|
55
|
+
},
|
|
47
56
|
];
|
|
48
57
|
|
|
49
58
|
/** Resource-level UI metadata: no network or extra origins needed (images
|
|
@@ -77,6 +86,11 @@ export const TOOL_VIEWS = {
|
|
|
77
86
|
},
|
|
78
87
|
clips_make: { uri: CLIPS_URI, invoking: "Starting clips…", invoked: "Making clips" },
|
|
79
88
|
clips_get: { uri: CLIPS_URI, invoking: "Loading clips…", invoked: "Clips loaded" },
|
|
89
|
+
recording_open_recorder: {
|
|
90
|
+
uri: RECORDER_URI,
|
|
91
|
+
invoking: "Opening the recorder…",
|
|
92
|
+
invoked: "Recorder open",
|
|
93
|
+
},
|
|
80
94
|
};
|
|
81
95
|
|
|
82
96
|
/** Tools only views call (hidden from the model by UI hosts). */
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// Recorder view (ui://pandastudio/recorder.html) for recording_open_recorder:
|
|
2
|
+
// the script the agent loaded into PandaStudio's teleprompter (editable, with
|
|
3
|
+
// a reading-time estimate), Open the recorder again, and "I've recorded it",
|
|
4
|
+
// which tells the agent to carry on with the take. The user always presses
|
|
5
|
+
// Record in PandaStudio itself; this view never starts a camera or mic.
|
|
6
|
+
|
|
7
|
+
import { viewPage } from "./viewKit.mjs";
|
|
8
|
+
|
|
9
|
+
export const RECORDER_URI = "ui://pandastudio/recorder.html";
|
|
10
|
+
|
|
11
|
+
const CSS = `
|
|
12
|
+
.head { margin-bottom: 10px; }
|
|
13
|
+
.status { margin-top: 8px; padding: 9px 11px; border-radius: var(--radius); background: var(--bg2); font-size: 13px; }
|
|
14
|
+
.status.error { color: var(--danger); }
|
|
15
|
+
.modes { display: flex; gap: 4px; margin-top: 10px; flex-wrap: wrap; }
|
|
16
|
+
.modes .btn.on { background: var(--fg); color: var(--bg); border-color: var(--fg); }
|
|
17
|
+
textarea { width: 100%; min-height: 150px; margin-top: 10px; padding: 10px 11px; border-radius: var(--radius);
|
|
18
|
+
border: 1px solid var(--line); background: var(--bg); color: var(--fg); font: 14px/1.5 var(--font); resize: vertical; }
|
|
19
|
+
.meta { margin-top: 4px; }
|
|
20
|
+
.acts { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
|
|
21
|
+
`;
|
|
22
|
+
|
|
23
|
+
/** Runs in the view. Self-contained: serialized into the page. */
|
|
24
|
+
function main(kit, ui) {
|
|
25
|
+
const { h, duration, actionButton, toast } = ui;
|
|
26
|
+
const app = document.getElementById("app");
|
|
27
|
+
const view = {
|
|
28
|
+
script: "",
|
|
29
|
+
mode: "camera",
|
|
30
|
+
orientation: null,
|
|
31
|
+
opened: false,
|
|
32
|
+
error: null,
|
|
33
|
+
sent: false,
|
|
34
|
+
};
|
|
35
|
+
const MODES = [
|
|
36
|
+
["camera", "Camera"],
|
|
37
|
+
["screen", "Screen"],
|
|
38
|
+
["both", "Screen + camera"],
|
|
39
|
+
];
|
|
40
|
+
const ORIENTATIONS = [
|
|
41
|
+
["portrait", "Portrait 9:16"],
|
|
42
|
+
["landscape", "Landscape 16:9"],
|
|
43
|
+
];
|
|
44
|
+
|
|
45
|
+
const estimate = (text) => {
|
|
46
|
+
const words = text.split(/\s+/).filter(Boolean).length;
|
|
47
|
+
return { words, readSeconds: Math.round((words / 150) * 60) };
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
async function open() {
|
|
51
|
+
const { data } = await kit.callTool("recording_open_recorder", {
|
|
52
|
+
...(view.script.trim() ? { script: view.script.trim() } : {}),
|
|
53
|
+
mode: view.mode,
|
|
54
|
+
...(view.orientation ? { orientation: view.orientation } : {}),
|
|
55
|
+
});
|
|
56
|
+
view.opened = !!data?.opened;
|
|
57
|
+
view.error = null;
|
|
58
|
+
toast("The recorder is open in PandaStudio.");
|
|
59
|
+
render();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function render() {
|
|
63
|
+
const est = estimate(view.script);
|
|
64
|
+
const area = h("textarea", {
|
|
65
|
+
"aria-label": "Script for the teleprompter",
|
|
66
|
+
placeholder: "The script to read on camera",
|
|
67
|
+
});
|
|
68
|
+
area.value = view.script;
|
|
69
|
+
area.addEventListener("input", () => {
|
|
70
|
+
view.script = area.value;
|
|
71
|
+
const e = estimate(view.script);
|
|
72
|
+
meta.textContent = e.words
|
|
73
|
+
? `${e.words} words · about ${duration(e.readSeconds * 1000)} to read`
|
|
74
|
+
: "No script: the recorder opens without the teleprompter.";
|
|
75
|
+
});
|
|
76
|
+
const meta = h(
|
|
77
|
+
"div",
|
|
78
|
+
{ class: "small faint meta" },
|
|
79
|
+
est.words
|
|
80
|
+
? `${est.words} words · about ${duration(est.readSeconds * 1000)} to read`
|
|
81
|
+
: "No script: the recorder opens without the teleprompter.",
|
|
82
|
+
);
|
|
83
|
+
app.replaceChildren(
|
|
84
|
+
h(
|
|
85
|
+
"div",
|
|
86
|
+
{ class: "head" },
|
|
87
|
+
h("div", { class: "brand" }, h("i"), "PandaStudio recorder"),
|
|
88
|
+
h(
|
|
89
|
+
"div",
|
|
90
|
+
{ class: view.error ? "status error" : "status" },
|
|
91
|
+
view.error ??
|
|
92
|
+
(view.opened
|
|
93
|
+
? "The recorder is open in PandaStudio with this script in the teleprompter. Press Record in the bar at the bottom of your screen; the script scrolls as you talk."
|
|
94
|
+
: "Opening the recorder in PandaStudio…"),
|
|
95
|
+
),
|
|
96
|
+
),
|
|
97
|
+
h(
|
|
98
|
+
"div",
|
|
99
|
+
{ class: "modes", role: "radiogroup", "aria-label": "What to record" },
|
|
100
|
+
MODES.map(([id, label]) =>
|
|
101
|
+
h(
|
|
102
|
+
"button",
|
|
103
|
+
{
|
|
104
|
+
type: "button",
|
|
105
|
+
class: view.mode === id ? "btn on" : "btn",
|
|
106
|
+
"aria-pressed": String(view.mode === id),
|
|
107
|
+
onclick: () => {
|
|
108
|
+
view.mode = id;
|
|
109
|
+
render();
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
label,
|
|
113
|
+
),
|
|
114
|
+
),
|
|
115
|
+
),
|
|
116
|
+
h(
|
|
117
|
+
"div",
|
|
118
|
+
{ class: "modes", role: "radiogroup", "aria-label": "Shape" },
|
|
119
|
+
ORIENTATIONS.map(([id, label]) =>
|
|
120
|
+
h(
|
|
121
|
+
"button",
|
|
122
|
+
{
|
|
123
|
+
type: "button",
|
|
124
|
+
class: view.orientation === id ? "btn on" : "btn",
|
|
125
|
+
"aria-pressed": String(view.orientation === id),
|
|
126
|
+
onclick: () => {
|
|
127
|
+
view.orientation = id;
|
|
128
|
+
render();
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
label,
|
|
132
|
+
),
|
|
133
|
+
),
|
|
134
|
+
),
|
|
135
|
+
area,
|
|
136
|
+
meta,
|
|
137
|
+
h(
|
|
138
|
+
"div",
|
|
139
|
+
{ class: "acts" },
|
|
140
|
+
actionButton(view.opened ? "Update and reopen" : "Open the recorder", open, {
|
|
141
|
+
primary: !view.opened,
|
|
142
|
+
busy: "Opening…",
|
|
143
|
+
}),
|
|
144
|
+
view.opened
|
|
145
|
+
? actionButton(
|
|
146
|
+
view.sent ? "Told the assistant" : "I've recorded it",
|
|
147
|
+
async () => {
|
|
148
|
+
if (view.sent) return;
|
|
149
|
+
await kit.sendMessage(
|
|
150
|
+
"I've finished recording in PandaStudio. Find the new take (project_current) and carry on.",
|
|
151
|
+
);
|
|
152
|
+
view.sent = true;
|
|
153
|
+
render();
|
|
154
|
+
},
|
|
155
|
+
{ primary: true, busy: "Sending…" },
|
|
156
|
+
)
|
|
157
|
+
: null,
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
render();
|
|
163
|
+
kit.on("input", (args) => {
|
|
164
|
+
if (typeof args?.script === "string") view.script = args.script;
|
|
165
|
+
if (args?.mode === "camera" || args?.mode === "screen" || args?.mode === "both")
|
|
166
|
+
view.mode = args.mode;
|
|
167
|
+
else view.mode = view.script.trim() ? "camera" : "both";
|
|
168
|
+
if (args?.orientation === "portrait" || args?.orientation === "landscape")
|
|
169
|
+
view.orientation = args.orientation;
|
|
170
|
+
render();
|
|
171
|
+
});
|
|
172
|
+
kit.on("result", (result) => {
|
|
173
|
+
view.error = kit.resultError(result);
|
|
174
|
+
const data = kit.resultData(result) ?? {};
|
|
175
|
+
view.opened = !view.error && !!data.opened;
|
|
176
|
+
if (data.mode) view.mode = data.mode;
|
|
177
|
+
if (data.orientation) view.orientation = data.orientation;
|
|
178
|
+
render();
|
|
179
|
+
});
|
|
180
|
+
kit.on("context", () => render());
|
|
181
|
+
kit.connect().catch((err) => {
|
|
182
|
+
view.error = `Couldn't connect to the chat: ${err?.message ?? err}`;
|
|
183
|
+
render();
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function recorderHtml(version) {
|
|
188
|
+
return viewPage({ title: "PandaStudio recorder", version, css: CSS, main });
|
|
189
|
+
}
|
package/bin/server.mjs
CHANGED
|
@@ -583,13 +583,25 @@ const MOTION_ELEMENT_TYPES = [
|
|
|
583
583
|
"ticker",
|
|
584
584
|
"credits",
|
|
585
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",
|
|
586
598
|
];
|
|
587
599
|
const MOTION_ELEMENT_PROPS = {
|
|
588
600
|
type: { type: "string", enum: MOTION_ELEMENT_TYPES },
|
|
589
601
|
content: {
|
|
590
602
|
type: ["object", "string", "array"],
|
|
591
603
|
description:
|
|
592
|
-
'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.',
|
|
593
605
|
},
|
|
594
606
|
atMs: { type: "number", description: "Start, edited ms. Or wordId." },
|
|
595
607
|
wordId: {
|
|
@@ -633,7 +645,12 @@ const MOTION_ELEMENT_PROPS = {
|
|
|
633
645
|
description:
|
|
634
646
|
"auto (clear of the face) | top | upper | center | lower | bottom | { x, y, w? } (% of frame; x,y = centre).",
|
|
635
647
|
},
|
|
636
|
-
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
|
+
},
|
|
637
654
|
sound: {
|
|
638
655
|
type: "string",
|
|
639
656
|
enum: ["auto", "none", "pop", "whoosh", "hit", "tick", "bell"],
|
|
@@ -3072,7 +3089,10 @@ const TOOLS = [
|
|
|
3072
3089
|
type: "boolean",
|
|
3073
3090
|
description: "Follow the recorded cursor (screen recordings).",
|
|
3074
3091
|
},
|
|
3075
|
-
soundUrl: {
|
|
3092
|
+
soundUrl: {
|
|
3093
|
+
type: "string",
|
|
3094
|
+
description: 'Default bundled:sound/motion-swoosh-up; "none".',
|
|
3095
|
+
},
|
|
3076
3096
|
soundVolume: { type: "number", description: "0-1." },
|
|
3077
3097
|
anchorSourceMs: ANCHOR_PROP,
|
|
3078
3098
|
anchorSourceEndMs: ANCHOR_END_PROP,
|
|
@@ -4738,7 +4758,8 @@ const TOOLS = [
|
|
|
4738
4758
|
},
|
|
4739
4759
|
frameRate: {
|
|
4740
4760
|
type: ["number", "string"],
|
|
4741
|
-
description:
|
|
4761
|
+
description:
|
|
4762
|
+
"auto (default) | 23.976 | 24 | 25 | 29.97 | 30 | 50 | 59.94 | 60 (25 / 50 for PAL) | source (fastest source).",
|
|
4742
4763
|
},
|
|
4743
4764
|
expectedRevision: { type: "number" },
|
|
4744
4765
|
},
|
|
@@ -6127,6 +6148,33 @@ const TOOLS = [
|
|
|
6127
6148
|
inputSchema: { type: "object", properties: {} },
|
|
6128
6149
|
command: "recording.list-sources",
|
|
6129
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
|
+
},
|
|
6130
6178
|
{
|
|
6131
6179
|
name: "recording_start",
|
|
6132
6180
|
description:
|
|
@@ -6382,7 +6430,7 @@ const TOOLS = [
|
|
|
6382
6430
|
{
|
|
6383
6431
|
name: "project_add_motion_element",
|
|
6384
6432
|
description:
|
|
6385
|
-
|
|
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 }.',
|
|
6386
6434
|
inputSchema: {
|
|
6387
6435
|
type: "object",
|
|
6388
6436
|
properties: {
|
|
@@ -7198,6 +7246,151 @@ const TOOLS = [
|
|
|
7198
7246
|
},
|
|
7199
7247
|
command: "recipe.prepare-reference",
|
|
7200
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
|
+
},
|
|
7201
7394
|
{
|
|
7202
7395
|
name: "recipe_pick",
|
|
7203
7396
|
description:
|
|
@@ -7842,7 +8035,8 @@ const TOOLS = [
|
|
|
7842
8035
|
},
|
|
7843
8036
|
frameRate: {
|
|
7844
8037
|
type: ["number", "string"],
|
|
7845
|
-
description:
|
|
8038
|
+
description:
|
|
8039
|
+
"23.976 | 24 | 25 | 29.97 | 30 | 50 | 59.94 | 60 | source | auto; omit = project setting.",
|
|
7846
8040
|
},
|
|
7847
8041
|
format: {
|
|
7848
8042
|
type: "string",
|
|
@@ -7897,6 +8091,24 @@ const TOOLS = [
|
|
|
7897
8091
|
},
|
|
7898
8092
|
command: "export.verify",
|
|
7899
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
|
+
},
|
|
7900
8112
|
{
|
|
7901
8113
|
name: "export_list",
|
|
7902
8114
|
description:
|
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",
|