@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/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 ───────────────────────────────────────────
@@ -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-fast; "none".' },
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 + 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 }.",
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: "Hero hits on the most stressed words. Default true.",
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: { type: "string", enum: ["short", "long"] },
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
- return {
8693
- tools: TOOLS.map((t) => ({
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.259.0",
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",