premiere-pro-mcp 1.17.0 → 1.18.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/CHANGELOG.md CHANGED
@@ -6,6 +6,49 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.18.0] - 2026-09-23
10
+
11
+ ### Added
12
+
13
+ - MOGRT text recipes default to `text_controls: "full"`: each text layer exposes
14
+ Font Size, Stroke Width, Fill Color, and Stroke Color controls plus named
15
+ Position, Scale, Rotation, Anchor Point, and Opacity Essential Graphics
16
+ controls. The create result reads back source-comp controller names.
17
+ `text_only` keeps the previous behavior. Font-family editing is not scriptable
18
+ and is reported as a known gap. (#618)
19
+ - `media_placeholder` MOGRT recipe imports a workspace-contained PNG, JPEG, MOV,
20
+ or MP4, requests a replaceable Essential Graphics media slot, and exposes its
21
+ transform controls. Not verified on a live After Effects or Premiere host.
22
+ (#619)
23
+ - `import_mogrt` accepts `text_values`, writes each named text control after
24
+ insertion (replacing only the text value so styling is kept), and reports
25
+ `verified`, `mismatch`, `missing_property`, or `committed_unverified` per
26
+ field. `get_mogrt_component` returns `textValue` for every parameter and an
27
+ optional `expected_values` audit that flags stale Headline text. (#617)
28
+
29
+ ### Fixed
30
+
31
+ - `encode_file` and `encode_project_item` pass natively typed arguments (String
32
+ paths, Boolean removal flag, Time in/out), fixing "Illegal Parameter type";
33
+ `encode_file` fails early when the preset file is missing. (#615)
34
+ - `set_xmp_metadata` passes numeric `XMPConst` options to
35
+ `XMPUtils.appendProperties`, fixing "Bad argument list". (#611)
36
+ - `manage_proxies` auto-discovery skips "Same as Project" proxy presets whose
37
+ output never reached `output_path`, and asks for `preset_path` when no safe
38
+ preset exists. (#610)
39
+ - `manage_metadata_uxp` and `create_subclip_uxp` find items inside nested bins.
40
+ (#612, #613)
41
+ - `set_scale_to_frame_size` accepts timeline clip IDs and reports `verified` or
42
+ `committed_unverified`. (#614)
43
+
44
+ ### Known limitations
45
+
46
+ - The Essential Graphics panel can display a stale Headline after a MOGRT text
47
+ write. The tools read stored values and cannot refresh the host panel (#616
48
+ remains open).
49
+ - These fixes are covered by mocked automated tests only; they are not verified
50
+ on a licensed Premiere or After Effects host.
51
+
9
52
  ## [1.17.0] - 2026-09-21
10
53
 
11
54
  ### Added
package/README.md CHANGED
@@ -108,10 +108,10 @@ The current source exposes 384 core tools for supported workflow steps spanning
108
108
 
109
109
  <a id="latest-release"></a>
110
110
 
111
- ### Latest release: 1.17.0
111
+ ### Latest release: 1.18.0
112
112
 
113
- The published v1.16.4 npm artifact contains **381 core tools**, 379 in its default profile,
114
- and 474 with a compatible UXP connection. The development catalog above can include
113
+ The published v1.17.0 npm artifact contains **384 core tools**, 382 in its default profile,
114
+ and 477 with a compatible UXP connection. The development catalog above can include
115
115
  unreleased work. See the [versioned facts and package provenance](https://premiere-pro-mcp.com/facts/).
116
116
 
117
117
  ### Try a bounded workflow
@@ -160,7 +160,7 @@ if the connection is unavailable.
160
160
  local Premiere processes. See the generated [supported action catalog](docs/supported-actions.md)
161
161
  for individual capability and verification contracts.
162
162
 
163
- See the [v1.17.0 release notes](https://github.com/leancoderkavy/premiere-pro-mcp/releases/tag/v1.17.0)
163
+ See the [v1.18.0 release notes](https://github.com/leancoderkavy/premiere-pro-mcp/releases/tag/v1.18.0)
164
164
  for complete details. Live installation in Premiere Pro still requires host verification.
165
165
 
166
166
  ### Current MCP protocol support
@@ -208,7 +208,7 @@ their bins, media rules, and organization rules before a facility uses one.
208
208
  > ### Install the published package (verify the name)
209
209
  >
210
210
  > ```bash
211
- > npm i -g premiere-pro-mcp@1.17.0
211
+ > npm i -g premiere-pro-mcp@1.18.0
212
212
  > ```
213
213
  >
214
214
  > This repository publishes only **`premiere-pro-mcp`**. A differently named package (`adobe-premiere-pro-mcp`) may also declare a `premiere-pro-mcp` executable. Before configuring a client, confirm:
@@ -216,7 +216,7 @@ their bins, media rules, and organization rules before a facility uses one.
216
216
  > | Check | Expected |
217
217
  > | --- | --- |
218
218
  > | Package name | `premiere-pro-mcp` (not `adobe-premiere-pro-mcp`) |
219
- > | Version | `1.17.0` |
219
+ > | Version | `1.18.0` |
220
220
  > | Homepage / repo | https://premiere-pro-mcp.com/ · https://github.com/leancoderkavy/premiere-pro-mcp |
221
221
  >
222
222
  > ```bash
@@ -229,9 +229,9 @@ their bins, media rules, and organization rules before a facility uses one.
229
229
 
230
230
  ### Easiest supported path: Claude Desktop
231
231
 
232
- 1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.17.0/premiere-pro-mcp-1.17.0.mcpb).
232
+ 1. Download the current [Claude Desktop bundle (`.mcpb`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.18.0/premiere-pro-mcp-1.18.0.mcpb).
233
233
  2. In Claude Desktop, open **Settings > Extensions > Advanced settings > Install Extension**, select the downloaded bundle, and restart Claude Desktop.
234
- 3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.17.0/MCPBridgeCEP.zxp). Open it with your trusted ZXP installer. If your computer has no ZXP installer, use the npm connector installer in **Advanced setup** below.
234
+ 3. Download the separate [signed Premiere connector (`.zxp`)](https://github.com/leancoderkavy/premiere-pro-mcp/releases/download/v1.18.0/MCPBridgeCEP.zxp). Open it with your trusted ZXP installer. If your computer has no ZXP installer, use the npm connector installer in **Advanced setup** below.
235
235
  4. Restart Premiere, open a project, then open **Window > Extensions > MCP for Adobe Premiere Pro**.
236
236
  5. In Claude, enter: `Safely check my Premiere connection with verify_premiere_connection. Make no changes.`
237
237
 
@@ -531,11 +531,11 @@ From a clone of this repository:
531
531
  ```bash
532
532
  codex plugin marketplace add .
533
533
  codex plugin add premiere-pro@premiere-pro-mcp
534
- npx -y premiere-pro-mcp@1.16.4 --install-cep
534
+ npx -y premiere-pro-mcp@1.17.0 --install-cep
535
535
  ```
536
536
 
537
537
  Restart Premiere Pro and start a new Codex session after installation. The plugin
538
- launches `premiere-pro-mcp@1.16.4` through `npx`; the separate CEP installation is
538
+ launches `premiere-pro-mcp@1.17.0` through `npx`; the separate CEP installation is
539
539
  required because the MCP server communicates with the running Premiere host through
540
540
  the local bridge.
541
541
 
@@ -566,7 +566,7 @@ For Claude Code, add this repository as a marketplace and install the plugin:
566
566
  Then install the Premiere bridge and start a new Claude Code session:
567
567
 
568
568
  ```bash
569
- npx -y premiere-pro-mcp@1.16.4 --install-cep
569
+ npx -y premiere-pro-mcp@1.17.0 --install-cep
570
570
  ```
571
571
 
572
572
  The Claude Code package lives in
@@ -1,7 +1,7 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.17.0" ExtensionBundleName="MCP for Adobe After Effects">
2
+ <ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.aftereffects.bridge" ExtensionBundleVersion="1.18.0" ExtensionBundleName="MCP for Adobe After Effects">
3
3
  <ExtensionList>
4
- <Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.17.0"/>
4
+ <Extension Id="com.mcp.aftereffects.bridge.panel" Version="1.18.0"/>
5
5
  </ExtensionList>
6
6
  <ExecutionEnvironment>
7
7
  <HostList>
Binary file
@@ -1,8 +1,8 @@
1
1
  <?xml version="1.0" encoding="UTF-8"?>
2
- <ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.17.0" ExtensionBundleName="MCP for Adobe Premiere Pro">
2
+ <ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.18.0" ExtensionBundleName="MCP for Adobe Premiere Pro">
3
3
  <ExtensionList>
4
- <Extension Id="com.mcp.premiere.bridge.panel" Version="1.17.0"/>
5
- <Extension Id="com.mcp.premiere.bridge.headless" Version="1.17.0"/>
4
+ <Extension Id="com.mcp.premiere.bridge.panel" Version="1.18.0"/>
5
+ <Extension Id="com.mcp.premiere.bridge.headless" Version="1.18.0"/>
6
6
  </ExtensionList>
7
7
  <ExecutionEnvironment>
8
8
  <HostList>
@@ -81,7 +81,7 @@
81
81
  <section class="update-section" aria-live="polite">
82
82
  <div class="update-copy">
83
83
  <span class="section-label">MCP updates</span>
84
- <strong id="updateTitle">Version 1.17.0</strong>
84
+ <strong id="updateTitle">Version 1.18.0</strong>
85
85
  <span id="updateDetail">Checking the global MCP server and connector release…</span>
86
86
  </div>
87
87
  <button id="btnUpdate" class="button button-update" onclick="handleUpdateClick()" type="button" aria-describedby="updateDetail" disabled>
@@ -7,7 +7,7 @@
7
7
  })(this, function () {
8
8
  "use strict";
9
9
 
10
- var CURRENT_VERSION = "1.17.0";
10
+ var CURRENT_VERSION = "1.18.0";
11
11
  var PACKAGE_NAME = "premiere-pro-mcp";
12
12
  var LATEST_PACKAGE_API = "https://registry.npmjs.org/" + PACKAGE_NAME;
13
13
  var LATEST_RELEASE_API =
@@ -520,17 +520,29 @@ function __findH264Preset() {
520
520
  return candidates[0].path;
521
521
  }
522
522
 
523
- function __findProxyPreset() {
523
+ // Candidate presets for manage_proxies auto-discovery, in preference order:
524
+ // Premiere's IngestPresets/Proxy files first, then AME's H.264 system presets.
525
+ // Adobe ships the Proxy folder presets with a "Same as Project" destination, so
526
+ // the TypeScript handler must content-scan every candidate (gzip-aware) before
527
+ // using one. ExtendScript cannot inflate gzipped .epr files, so no filtering here.
528
+ function __listProxyPresetCandidates() {
529
+ var out = [];
524
530
  var ppro = __adobeAppFolders("Adobe Premiere Pro");
525
531
  for (var i = 0; i < ppro.length; i++) {
526
532
  var proxyDir = __adobeApplicationResourceFolder(ppro[i], "Settings/IngestPresets/Proxy");
527
533
  var eprs = __collectEprFiles(proxyDir, []);
528
- if (eprs.length) {
529
- eprs.sort(function(a, b) { return a.displayName < b.displayName ? -1 : 1; });
530
- return eprs[0].fsName;
534
+ eprs.sort(function(a, b) { return a.displayName < b.displayName ? -1 : 1; });
535
+ for (var e = 0; e < eprs.length; e++) out.push(eprs[e].fsName);
536
+ }
537
+ var presets = __collectAllPresets();
538
+ for (var p = 0; p < presets.length; p++) {
539
+ var haystack = (presets[p].name + " " + presets[p].format).toLowerCase();
540
+ if (presets[p].path.indexOf("IngestPresets") !== -1) continue;
541
+ if (haystack.indexOf("h264") !== -1 || haystack.indexOf("h.264") !== -1 || haystack.indexOf("48323634") !== -1) {
542
+ out.push(presets[p].path);
531
543
  }
532
544
  }
533
- return "";
545
+ return out;
534
546
  }
535
547
 
536
548
  function __findStillPreset(outputPath) {
@@ -35,7 +35,7 @@ import { toNodeHandler } from "@modelcontextprotocol/node";
35
35
  import { createMcpHandler } from "@modelcontextprotocol/server";
36
36
  import { createServer } from "./server.js";
37
37
  import { cleanupTempDir, getTempDir } from "./bridge/file-bridge.js";
38
- import { getTelemetry } from "./telemetry.js";
38
+ import { getTelemetry, telemetryErrorType } from "./telemetry.js";
39
39
  import { createHomepageExperiment, injectFirstPaintExposure } from "./homepage-experiment.js";
40
40
  import { shouldGzipLanding } from "./landing-compression.js";
41
41
  import { LandingDocumentRenderer, assertLandingDocumentSize, readBoundedUtf8, readLandingDocumentSettings, } from "./landing-documents.js";
@@ -482,7 +482,7 @@ const httpServer = http.createServer(async (req, res) => {
482
482
  method: req.method ?? "unknown",
483
483
  status_code: 500,
484
484
  duration_ms: Date.now() - requestStartedAt,
485
- error_type: err instanceof Error ? err.name : "UnknownError",
485
+ error_type: telemetryErrorType(err),
486
486
  });
487
487
  console.error("[premiere-pro-mcp] Request error:", err);
488
488
  if (!res.headersSent) {
package/dist/server.js CHANGED
@@ -67,7 +67,7 @@ import { buildPremiereInstructions } from "./workflows/agent-instructions.js";
67
67
  import { WORKFLOW_PROMPTS, WORKFLOW_RESOURCE } from "./workflows/catalog.js";
68
68
  import { annotationsForTool, structuredToolResult, } from "./workflows/tool-metadata.js";
69
69
  import { isToolInSelectedPacks, resolveToolPacks, } from "./workflows/tool-packs.js";
70
- import { getTelemetry } from "./telemetry.js";
70
+ import { getTelemetry, telemetryErrorType } from "./telemetry.js";
71
71
  import { z } from "zod";
72
72
  import { readFileSync } from "node:fs";
73
73
  import { fileURLToPath } from "node:url";
@@ -360,7 +360,7 @@ export function createServer(bridgeOptions, serverOptions = {}) {
360
360
  tool: name,
361
361
  outcome: "failed",
362
362
  duration_ms: Date.now() - startedAt,
363
- error_type: err instanceof Error ? err.name : "UnknownError",
363
+ error_type: telemetryErrorType(err),
364
364
  });
365
365
  return {
366
366
  structuredContent: structuredToolResult(name, false, undefined, err instanceof Error ? err.message : String(err)),
@@ -4,6 +4,8 @@ export interface Telemetry {
4
4
  capture(event: string, properties?: TelemetryProperties): void;
5
5
  shutdown(): Promise<void>;
6
6
  }
7
+ /** Error names are mutable and may contain host data; never transmit them raw. */
8
+ export declare function telemetryErrorType(error: unknown): string;
7
9
  /**
8
10
  * Activation telemetry is deliberately narrower than operational tool
9
11
  * telemetry. Emit it only after the read-only connection check has confirmed
package/dist/telemetry.js CHANGED
@@ -1,5 +1,13 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { PostHog } from "posthog-node";
3
+ /** Error names are mutable and may contain host data; never transmit them raw. */
4
+ export function telemetryErrorType(error) {
5
+ if (!(error instanceof Error))
6
+ return "UnknownError";
7
+ return ["Error", "TypeError", "RangeError", "SyntaxError", "ReferenceError", "URIError", "EvalError", "AggregateError"].includes(error.name)
8
+ ? error.name
9
+ : "Error";
10
+ }
3
11
  /**
4
12
  * Activation telemetry is deliberately narrower than operational tool
5
13
  * telemetry. Emit it only after the read-only connection check has confirmed
@@ -59,6 +67,7 @@ export function getTelemetry() {
59
67
  `server-${randomUUID()}`;
60
68
  const client = new PostHog(apiKey, {
61
69
  host,
70
+ disableGeoip: true,
62
71
  // MCP servers may be idle for long stretches. Flush each bounded operational
63
72
  // event promptly so a low-volume deployment remains observable.
64
73
  flushAt: 1,
@@ -567,11 +567,19 @@ export declare function getAdvancedTools(bridgeOptions: BridgeOptions): {
567
567
  type: string;
568
568
  description: string;
569
569
  };
570
+ expected_values: {
571
+ type: string;
572
+ description: string;
573
+ additionalProperties: {
574
+ type: string;
575
+ };
576
+ };
570
577
  };
571
578
  required: string[];
572
579
  };
573
580
  handler: (args: {
574
581
  node_id: string;
582
+ expected_values?: Record<string, string>;
575
583
  }) => Promise<import("../bridge/file-bridge.js").CommandResult>;
576
584
  };
577
585
  };
@@ -1,5 +1,6 @@
1
1
  import { buildToolScript, escapeForExtendScript, } from "../bridge/script-builder.js";
2
2
  import { sendCommand } from "../bridge/file-bridge.js";
3
+ import { compareMogrtText, extractMogrtText, summarizeMogrtText, validateMogrtTextMap } from "./mogrt-text.js";
3
4
  import { SPEED_UNAVAILABLE_DESCRIPTION, SPEED_UNAVAILABLE_ERROR } from "./timeline.js";
4
5
  export function getAdvancedTools(bridgeOptions) {
5
6
  return {
@@ -1487,7 +1488,7 @@ export function getAdvancedTools(bridgeOptions) {
1487
1488
  },
1488
1489
  },
1489
1490
  get_mogrt_component: {
1490
- description: "Get MOGRT (Motion Graphics Template) component parameters from a clip",
1491
+ description: "Get MOGRT (Motion Graphics Template) component parameters from a clip. Each parameter includes textValue (visible text extracted from textEditValue when present). Pass expected_values to audit text controls such as Headline; the result flags verified, mismatch, or missing_property per field. Reads the stored property, not the Essential Graphics panel display, which Premiere can show stale (host UI behavior this tool cannot refresh).",
1491
1492
  parameters: {
1492
1493
  type: "object",
1493
1494
  properties: {
@@ -1495,10 +1496,16 @@ export function getAdvancedTools(bridgeOptions) {
1495
1496
  type: "string",
1496
1497
  description: "Node ID of the MOGRT clip on the timeline",
1497
1498
  },
1499
+ expected_values: {
1500
+ type: "object",
1501
+ description: "Optional read-only audit map of MOGRT text parameter display names to the text each should contain (for example { \"Headline\": \"Chapter 3\" }). Result audit.status is verified, mismatch, or missing_property.",
1502
+ additionalProperties: { type: "string" },
1503
+ },
1498
1504
  },
1499
1505
  required: ["node_id"],
1500
1506
  },
1501
1507
  handler: async (args) => {
1508
+ const expected = validateMogrtTextMap(args.expected_values, "expected_values");
1502
1509
  const script = buildToolScript(`
1503
1510
  var result = __findClip("${escapeForExtendScript(args.node_id)}");
1504
1511
  if (!result) return __error("Clip not found");
@@ -1517,7 +1524,27 @@ export function getAdvancedTools(bridgeOptions) {
1517
1524
 
1518
1525
  return __result({ clipName: result.clip.name, parameters: params });
1519
1526
  `);
1520
- return sendCommand(script, bridgeOptions);
1527
+ const response = await sendCommand(script, bridgeOptions);
1528
+ if (!response.success)
1529
+ return response;
1530
+ const data = (response.data ?? {});
1531
+ const rawParams = Array.isArray(data.parameters) ? data.parameters : [];
1532
+ const parameters = rawParams.map((p) => ({ ...p, textValue: extractMogrtText(p.value) }));
1533
+ if (!expected)
1534
+ return { ...response, data: { ...data, parameters } };
1535
+ const checks = compareMogrtText(expected, parameters);
1536
+ return {
1537
+ ...response,
1538
+ data: {
1539
+ ...data,
1540
+ parameters,
1541
+ audit: {
1542
+ status: summarizeMogrtText(checks),
1543
+ checks,
1544
+ scope: "Stored MOGRT property values only; the Essential Graphics panel display is not read.",
1545
+ },
1546
+ },
1547
+ };
1521
1548
  },
1522
1549
  },
1523
1550
  };
@@ -790,7 +790,11 @@ export declare function getExportTools(bridgeOptions: BridgeOptions): {
790
790
  proxy_path?: string;
791
791
  output_path?: string;
792
792
  preset_path?: string;
793
- }) => Promise<import("../bridge/file-bridge.js").CommandResult>;
793
+ }) => Promise<import("../bridge/file-bridge.js").CommandResult | {
794
+ success: boolean;
795
+ error: string;
796
+ skippedPresetCount: number;
797
+ }>;
794
798
  };
795
799
  };
796
800
  //# sourceMappingURL=export.d.ts.map
@@ -1331,7 +1331,7 @@ export function getExportTools(bridgeOptions) {
1331
1331
  outputFile.fsName,
1332
1332
  "${escapeForExtendScript(args.preset_path)}",
1333
1333
  app.encoder.ENCODE_IN_TO_OUT,
1334
- ${args.remove_on_completion !== false ? 1 : 0}
1334
+ ${args.remove_on_completion !== false ? "true" : "false"}
1335
1335
  );
1336
1336
  if (!jobId || String(jobId) === "0") return __error("Adobe Media Encoder did not queue the project-item export.");
1337
1337
  app.encoder.startBatch();
@@ -1411,13 +1411,18 @@ export function getExportTools(bridgeOptions) {
1411
1411
  var srcOut = new Time();
1412
1412
  srcOut.seconds = ${outSeconds};
1413
1413
  var workArea = ${hasRange ? 1 : 0};
1414
-
1414
+ var removeUponCompletion = ${args.remove_on_completion !== false ? "true" : "false"};
1415
+ var presetFile = new File("${escapeForExtendScript(args.preset_path)}");
1416
+ if (!presetFile.exists) return __error("AME preset file does not exist: " + presetFile.fsName);
1417
+
1418
+ // Premiere type-checks encodeFile natively: paths must be Strings,
1419
+ // workArea a Number, removeUponCompletion a Boolean, and in/out Time objects.
1415
1420
  var jobId = app.encoder.encodeFile(
1416
- inputFile.fsName,
1417
- outputFile.fsName,
1418
- "${escapeForExtendScript(args.preset_path)}",
1421
+ String(inputFile.fsName),
1422
+ String(outputFile.fsName),
1423
+ String(presetFile.fsName),
1419
1424
  workArea,
1420
- ${args.remove_on_completion !== false ? 1 : 0},
1425
+ removeUponCompletion,
1421
1426
  srcIn,
1422
1427
  srcOut
1423
1428
  );
@@ -1467,12 +1472,44 @@ export function getExportTools(bridgeOptions) {
1467
1472
  preset_path: {
1468
1473
  type: "string",
1469
1474
  description: "Path to a proxy ingest preset (.epr) for 'create'. " +
1470
- "If omitted, the first preset found in Premiere's IngestPresets/Proxy folder is used.",
1475
+ "If omitted, the server lists Premiere's IngestPresets/Proxy presets and AME H.264 system presets, " +
1476
+ "skips every preset whose output destination is Same as Project (Adobe's shipped proxy presets are), " +
1477
+ "and uses the first remaining one. Fails with a clear error when none qualify.",
1471
1478
  },
1472
1479
  },
1473
1480
  required: ["item_id", "action"],
1474
1481
  },
1475
1482
  handler: async (args) => {
1483
+ let presetPath = args.preset_path;
1484
+ const skippedPresets = [];
1485
+ if (args.action === "create" && args.output_path && !presetPath) {
1486
+ const listing = await sendCommand(buildToolScript(`return __result({ candidates: __listProxyPresetCandidates() });`), bridgeOptions);
1487
+ if (!listing.success)
1488
+ return listing;
1489
+ const rawCandidates = listing.data?.candidates;
1490
+ const candidates = Array.isArray(rawCandidates)
1491
+ ? rawCandidates.filter((value) => typeof value === "string" && value.length > 0)
1492
+ : [];
1493
+ for (const candidate of candidates) {
1494
+ try {
1495
+ presetPath = inspectExportPresetFile(candidate).path;
1496
+ break;
1497
+ }
1498
+ catch {
1499
+ skippedPresets.push(candidate);
1500
+ }
1501
+ }
1502
+ if (!presetPath) {
1503
+ return {
1504
+ success: false,
1505
+ error: candidates.length === 0
1506
+ ? "Could not locate any proxy or H.264 export preset. Pass preset_path explicitly (an .epr with an explicit output destination)."
1507
+ : `All ${candidates.length} auto-discovered presets use a Same as Project output destination or could not be read, so AME would not write the proxy to output_path. Pass preset_path explicitly (an .epr with an explicit output destination).`,
1508
+ skippedPresetCount: skippedPresets.length,
1509
+ };
1510
+ }
1511
+ }
1512
+ const autoDiscovered = presetPath !== args.preset_path;
1476
1513
  const script = buildToolScript(`
1477
1514
  var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
1478
1515
  if (!item) return __error("Item not found");
@@ -1483,12 +1520,7 @@ export function getExportTools(bridgeOptions) {
1483
1520
  ${!args.output_path
1484
1521
  ? `return __error("output_path is required for the 'create' action");`
1485
1522
  : `var outputPath = "${escapeForExtendScript(args.output_path)}";
1486
- ${args.preset_path
1487
- ? `var presetPath = "${escapeForExtendScript(args.preset_path)}";`
1488
- : `var presetPath = __findProxyPreset();
1489
- if (!presetPath) {
1490
- return __error("Could not locate a proxy ingest preset. Pass preset_path explicitly (an .epr under Premiere's Settings/IngestPresets/Proxy folder).");
1491
- }`}
1523
+ var presetPath = "${escapeForExtendScript(presetPath ?? "")}";
1492
1524
 
1493
1525
  // ProjectItem has no createProxy(). Proxy generation must go through
1494
1526
  // Adobe Media Encoder; the result is attached in a separate step once
@@ -1507,6 +1539,8 @@ export function getExportTools(bridgeOptions) {
1507
1539
  jobId: String(jobId),
1508
1540
  outputPath: outputPath,
1509
1541
  presetUsed: presetPath,
1542
+ presetAutoDiscovered: ${autoDiscovered ? "true" : "false"},
1543
+ skippedSameAsProjectPresets: ${skippedPresets.length},
1510
1544
  verificationScope: "Premiere returned an AME job ID. Queue presence and proxy-file creation are not verified by this tool.",
1511
1545
  nextStep: "Verify the proxy file exists, then call manage_proxies with action 'attach' and proxy_path set to outputPath."
1512
1546
  });`}
@@ -460,23 +460,75 @@ export function getMediaTools(bridgeOptions) {
460
460
  },
461
461
  },
462
462
  set_scale_to_frame_size: {
463
- description: "Enable 'Scale to Frame Size' on a project item so it fills the sequence frame",
463
+ description: "Enable 'Scale to Frame Size' so media fills the sequence frame. Accepts a timeline clip node ID " +
464
+ "(active sequence, same IDs as get_clip_properties) or a project item node ID or name. A timeline " +
465
+ "clip is resolved first and its source project item is updated through ProjectItem.setScaleToFrameSize(). " +
466
+ "ExtendScript exposes no getter for this flag, so the result is committed_unverified unless the timeline " +
467
+ "clip's Motion > Scale reads back changed (verified). Check with get_clip_properties.",
464
468
  parameters: {
465
469
  type: "object",
466
470
  properties: {
467
471
  item_id: {
468
472
  type: "string",
469
- description: "Node ID or name of the project item",
473
+ description: "Timeline clip node ID in the active sequence, or project item node ID or name",
470
474
  },
471
475
  },
472
476
  required: ["item_id"],
473
477
  },
474
478
  handler: async (args) => {
475
479
  const script = buildToolScript(`
476
- var item = __findProjectItem("${escapeForExtendScript(args.item_id)}");
477
- if (!item) return __error("Item not found");
480
+ var requestedId = "${escapeForExtendScript(args.item_id)}";
481
+ function __motionScale(trackItem) {
482
+ try {
483
+ var comps = trackItem.components;
484
+ for (var ci = 0; ci < comps.numItems; ci++) {
485
+ var comp = comps[ci];
486
+ if (comp.displayName !== "Motion" && comp.matchName !== "AE.ADBE Motion") continue;
487
+ for (var pi = 0; pi < comp.properties.numItems; pi++) {
488
+ var prop = comp.properties[pi];
489
+ if (prop.displayName === "Scale") return prop.getValue();
490
+ }
491
+ }
492
+ } catch (e) {}
493
+ return null;
494
+ }
495
+ var target = "projectItem";
496
+ var trackItem = null;
497
+ var trackInfo = null;
498
+ var item = null;
499
+ var found = __findClip(requestedId);
500
+ if (found) {
501
+ target = "timelineClip";
502
+ trackItem = found.clip;
503
+ trackInfo = { trackType: found.trackType, trackIndex: found.trackIndex, clipIndex: found.clipIndex };
504
+ try { item = trackItem.projectItem; } catch (e) { item = null; }
505
+ if (!item) return __error("Timeline clip " + requestedId + " has no source project item");
506
+ } else {
507
+ item = __findProjectItem(requestedId);
508
+ }
509
+ if (!item) return __error("Item not found: no timeline clip in the active sequence or project item matches " + requestedId);
510
+ var scaleBefore = trackItem ? __motionScale(trackItem) : null;
478
511
  item.setScaleToFrameSize();
479
- return __result({ set: true, item: item.name });
512
+ var scaleAfter = trackItem ? __motionScale(trackItem) : null;
513
+ var changed = scaleBefore !== null && scaleAfter !== null && scaleBefore !== scaleAfter;
514
+ var out = {
515
+ set: true,
516
+ target: target,
517
+ item: item.name,
518
+ projectItemNodeId: __nodeIdOf(item),
519
+ status: changed ? "verified" : "committed_unverified",
520
+ motionScaleBefore: scaleBefore,
521
+ motionScaleAfter: scaleAfter
522
+ };
523
+ if (trackItem) {
524
+ out.clipNodeId = String(trackItem.nodeId);
525
+ out.clipName = trackItem.name;
526
+ out.trackType = trackInfo.trackType;
527
+ out.trackIndex = trackInfo.trackIndex;
528
+ out.clipIndex = trackInfo.clipIndex;
529
+ }
530
+ if (!changed) out.note = "ExtendScript has no getter for Scale to Frame Size; confirm with get_clip_properties.";
531
+ return __result(out);
480
532
  `);
481
533
  return sendCommand(script, bridgeOptions);
482
534
  },
@@ -485,7 +485,8 @@ export function getMetadataTools(bridgeOptions) {
485
485
  } catch (e) {
486
486
  return __error("Premiere could not load the Adobe XMP library; no metadata was changed: " + e.toString());
487
487
  }
488
- if (typeof XMPMeta !== "function" || typeof XMPUtils === "undefined" || typeof XMPUtils.appendProperties !== "function") {
488
+ if (typeof XMPMeta !== "function" || typeof XMPUtils === "undefined" || typeof XMPUtils.appendProperties !== "function" ||
489
+ typeof XMPConst === "undefined" || typeof XMPConst.APPEND_ALL_PROPERTIES !== "number" || typeof XMPConst.APPEND_REPLACE_OLD_VALUES !== "number") {
489
490
  return __error("Premiere's XMP merge APIs are unavailable; no metadata was changed.");
490
491
  }
491
492
 
@@ -496,7 +497,9 @@ export function getMetadataTools(bridgeOptions) {
496
497
  var patchXmp = new XMPMeta("${escapeForExtendScript(args.xmp_xml)}");
497
498
  // Copy every supplied top-level field into the existing packet, replacing
498
499
  // only fields named by the patch and retaining unrelated metadata.
499
- XMPUtils.appendProperties(patchXmp, existingXmp, true, true, false);
500
+ // AdobeXMPScript signature: appendProperties(source, dest, options: Number).
501
+ var appendOptions = XMPConst.APPEND_ALL_PROPERTIES | XMPConst.APPEND_REPLACE_OLD_VALUES;
502
+ XMPUtils.appendProperties(patchXmp, existingXmp, appendOptions);
500
503
  item.setXMPMetadata(existingXmp.serialize());
501
504
 
502
505
  // Reparse the host readback so a malformed or rejected packet never