@writepanda/mcp 1.259.0 → 1.262.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 +242 -0
- package/bin/apps/viewKit.mjs +506 -0
- package/bin/server.mjs +91 -11
- 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 ───────────────────────────────────────────
|
|
@@ -1705,6 +1719,19 @@ const TOOLS = [
|
|
|
1705
1719
|
},
|
|
1706
1720
|
command: "project.show",
|
|
1707
1721
|
},
|
|
1722
|
+
{
|
|
1723
|
+
name: "project_edit_summary",
|
|
1724
|
+
description:
|
|
1725
|
+
"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.",
|
|
1726
|
+
inputSchema: {
|
|
1727
|
+
type: "object",
|
|
1728
|
+
properties: {
|
|
1729
|
+
id: { type: "string" },
|
|
1730
|
+
path: { type: "string" },
|
|
1731
|
+
},
|
|
1732
|
+
},
|
|
1733
|
+
command: "project.edit-summary",
|
|
1734
|
+
},
|
|
1708
1735
|
{
|
|
1709
1736
|
name: "matte_status",
|
|
1710
1737
|
description:
|
|
@@ -3045,7 +3072,7 @@ const TOOLS = [
|
|
|
3045
3072
|
type: "boolean",
|
|
3046
3073
|
description: "Follow the recorded cursor (screen recordings).",
|
|
3047
3074
|
},
|
|
3048
|
-
soundUrl: { type: "string", description: 'Default bundled:sound/swoosh-
|
|
3075
|
+
soundUrl: { type: "string", description: 'Default bundled:sound/motion-swoosh-up; "none".' },
|
|
3049
3076
|
soundVolume: { type: "number", description: "0-1." },
|
|
3050
3077
|
anchorSourceMs: ANCHOR_PROP,
|
|
3051
3078
|
anchorSourceEndMs: ANCHOR_END_PROP,
|
|
@@ -6315,7 +6342,7 @@ const TOOLS = [
|
|
|
6315
6342
|
{
|
|
6316
6343
|
name: "project_compose_soundtrack",
|
|
6317
6344
|
description:
|
|
6318
|
-
"Score this edit: music
|
|
6345
|
+
"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
6346
|
inputSchema: {
|
|
6320
6347
|
type: "object",
|
|
6321
6348
|
properties: {
|
|
@@ -6327,7 +6354,8 @@ const TOOLS = [
|
|
|
6327
6354
|
sfx: { type: "boolean", description: "Default true." },
|
|
6328
6355
|
useSpeech: {
|
|
6329
6356
|
type: "boolean",
|
|
6330
|
-
description:
|
|
6357
|
+
description:
|
|
6358
|
+
"Hits on the most stressed words even with nothing on screen. Default true for promo only.",
|
|
6331
6359
|
},
|
|
6332
6360
|
cues: {
|
|
6333
6361
|
type: "array",
|
|
@@ -7173,13 +7201,17 @@ const TOOLS = [
|
|
|
7173
7201
|
{
|
|
7174
7202
|
name: "recipe_pick",
|
|
7175
7203
|
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.",
|
|
7204
|
+
"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
7205
|
inputSchema: {
|
|
7178
7206
|
type: "object",
|
|
7179
7207
|
properties: {
|
|
7180
7208
|
id: { type: "string", description: "Project id (default: the open project)." },
|
|
7181
7209
|
path: { type: "string" },
|
|
7182
|
-
format: {
|
|
7210
|
+
format: {
|
|
7211
|
+
type: "string",
|
|
7212
|
+
enum: ["short", "long"],
|
|
7213
|
+
description: "Default from the project's aspect ratio (9:16 = short).",
|
|
7214
|
+
},
|
|
7183
7215
|
footage: {
|
|
7184
7216
|
type: "string",
|
|
7185
7217
|
enum: ["camera", "screen", "podcast", "audio", "none"],
|
|
@@ -8001,6 +8033,24 @@ const TOOLS = [
|
|
|
8001
8033
|
},
|
|
8002
8034
|
command: "clips.delete",
|
|
8003
8035
|
},
|
|
8036
|
+
{
|
|
8037
|
+
name: "clips_remove_clip",
|
|
8038
|
+
description:
|
|
8039
|
+
"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 }.",
|
|
8040
|
+
inputSchema: {
|
|
8041
|
+
type: "object",
|
|
8042
|
+
properties: {
|
|
8043
|
+
id: { type: "string", description: "Clip set id." },
|
|
8044
|
+
clipId: { type: "string", description: "The clip's id (from clips_get)." },
|
|
8045
|
+
keepProject: {
|
|
8046
|
+
type: "boolean",
|
|
8047
|
+
description: "Keep the clip's project in Projects instead of the Trash. Default false.",
|
|
8048
|
+
},
|
|
8049
|
+
},
|
|
8050
|
+
required: ["id", "clipId"],
|
|
8051
|
+
},
|
|
8052
|
+
command: "clips.remove-clip",
|
|
8053
|
+
},
|
|
8004
8054
|
{
|
|
8005
8055
|
name: "ideas_research",
|
|
8006
8056
|
description:
|
|
@@ -8654,8 +8704,9 @@ Before an editing pass, read the skill's "Which tool for which moment" section (
|
|
|
8654
8704
|
3. Fix misheard names / brands with transcript_find_replace (transcript_insert_words for dropped words).
|
|
8655
8705
|
4. transcript_find_issues, then transcript_delete_words on each duplicate take / false start (keep the LAST take; keep severity "low" ones).
|
|
8656
8706
|
5. transcript_remove_silences.
|
|
8707
|
+
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
8708
|
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.
|
|
8709
|
+
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
8710
|
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
8711
|
9. llm_generate_title / description / timestamps, then preview_show and ask before export_start. Never export before the user has seen the preview.
|
|
8661
8712
|
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 +8720,8 @@ Publishing has daily limits (YouTube uploads and updates per license, social pos
|
|
|
8669
8720
|
|
|
8670
8721
|
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
8722
|
|
|
8723
|
+
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.
|
|
8724
|
+
|
|
8672
8725
|
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
8726
|
|
|
8674
8727
|
Destination decides everything: infer it (Shorts/TikTok/vertical/<=90s: shorts; YouTube/tutorial/>8 min: youtube-long; LinkedIn; Loom/internal), else ask once.
|
|
@@ -8683,23 +8736,49 @@ const server = new Server(
|
|
|
8683
8736
|
{
|
|
8684
8737
|
capabilities: {
|
|
8685
8738
|
tools: {},
|
|
8739
|
+
// Only the ui:// views (MCP Apps), listed for clients that render them.
|
|
8740
|
+
resources: {},
|
|
8686
8741
|
},
|
|
8687
8742
|
instructions: INSTRUCTIONS,
|
|
8688
8743
|
},
|
|
8689
8744
|
);
|
|
8690
8745
|
|
|
8746
|
+
/** Whether the connected client renders MCP Apps views (apps/index.mjs). */
|
|
8747
|
+
const uiEnabled = () => clientSupportsUi(server.getClientCapabilities(), { inApp: IN_APP });
|
|
8748
|
+
/** Image paths the app reported this session, served to views by ui_read_media. */
|
|
8749
|
+
const media = new MediaRegistry();
|
|
8750
|
+
|
|
8691
8751
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
8692
|
-
|
|
8693
|
-
|
|
8752
|
+
const ui = uiEnabled();
|
|
8753
|
+
const tools = TOOLS.map((t) => {
|
|
8754
|
+
const meta = ui ? toolUiMeta(t.name) : undefined;
|
|
8755
|
+
return {
|
|
8694
8756
|
name: t.name,
|
|
8695
8757
|
description: t.description,
|
|
8696
8758
|
// Gemini rejects multi-type fields that JSON Schema allows.
|
|
8697
8759
|
inputSchema: portableSchema(t.inputSchema),
|
|
8698
|
-
|
|
8699
|
-
|
|
8760
|
+
...(meta ? { _meta: meta } : {}),
|
|
8761
|
+
};
|
|
8762
|
+
});
|
|
8763
|
+
return { tools: ui ? [...tools, ...APP_TOOLS] : tools };
|
|
8764
|
+
});
|
|
8765
|
+
|
|
8766
|
+
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
8767
|
+
resources: uiEnabled() ? listUiResources() : [],
|
|
8768
|
+
}));
|
|
8769
|
+
server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
|
|
8770
|
+
resourceTemplates: [],
|
|
8771
|
+
}));
|
|
8772
|
+
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
8773
|
+
const res = readUiResource(request.params.uri, mcpVersion());
|
|
8774
|
+
if (!res) throw new Error(`unknown resource: ${request.params.uri}`);
|
|
8775
|
+
return res;
|
|
8700
8776
|
});
|
|
8701
8777
|
|
|
8702
8778
|
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
8779
|
+
if (request.params.name === "ui_read_media") {
|
|
8780
|
+
return media.read(request.params.arguments?.paths);
|
|
8781
|
+
}
|
|
8703
8782
|
const tool = TOOLS.find((t) => t.name === request.params.name);
|
|
8704
8783
|
if (!tool) {
|
|
8705
8784
|
return {
|
|
@@ -8753,6 +8832,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
|
8753
8832
|
];
|
|
8754
8833
|
try {
|
|
8755
8834
|
const data = result?.data ?? result;
|
|
8835
|
+
media.note(data);
|
|
8756
8836
|
const previews = collectInlinePreviewPaths(command, data);
|
|
8757
8837
|
for (const previewPath of previews) {
|
|
8758
8838
|
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.262.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",
|