premiere-pro-mcp 1.4.0 → 1.5.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,30 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [1.5.0] - 2026-07-30
10
+
11
+ ### Added
12
+
13
+ - Added `detect_silence` for finding dead air in local source media with FFmpeg, including
14
+ Docker support and clear local-install guidance.
15
+ - Added anonymous, opt-out PostHog usage telemetry with prompt flushing for low-volume servers.
16
+ - Added an immersive editorial landing-page experience, product demo video, changelog page, and
17
+ a 30-day launch plan.
18
+
19
+ ### Changed
20
+
21
+ - Expanded the MCP surface to 279 tools and limited advertised tools to those allowed by the
22
+ active capability profile.
23
+ - Documented capability-filtered discovery, remote media-path constraints, and the difference
24
+ between the 279 registered tools and the 277 tools available to the default profile.
25
+
26
+ ### Fixed
27
+
28
+ - Structural timeline tools now verify razor, ripple-delete, transition, and track-targeting
29
+ mutations instead of reporting success when Premiere applied only part or none of an edit.
30
+ - Server metadata now reports the package version rather than a stale hard-coded value.
31
+ - Resolved CodeQL findings in HTTP authentication and filesystem-path handling.
32
+
9
33
  ## [1.4.0] - 2026-07-26
10
34
 
11
35
  ### Added
package/README.md CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  # Premiere Pro MCP Server
4
4
 
5
+ [![MCP Toplist](https://mcptoplist.com/badge/glama%2Fleancoderkavy%2Fpremiere-pro-mcp.svg)](https://mcptoplist.com/server/glama%2Fleancoderkavy%2Fpremiere-pro-mcp)
6
+
5
7
  **Give AI full control over Adobe Premiere Pro.**
6
8
 
7
- 278 tools across 31 modules, 3 resources, and 4 guided workflows.
9
+ 279 tools across 31 modules, 3 resources, and 4 guided workflows.
8
10
 
9
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
10
12
  [![Node.js](https://img.shields.io/badge/Node.js-20.19%2B-green.svg)](https://nodejs.org)
@@ -25,7 +27,7 @@ An [MCP (Model Context Protocol)](https://modelcontextprotocol.io) server that l
25
27
  "Add the B-roll clips to V2, apply a cross dissolve between each, color correct them to match the A-roll, and export a 1080p ProRes."
26
28
  ```
27
29
 
28
- The AI handles the entire workflow through 278 tools spanning the supported ExtendScript, QE DOM, and safe edit-planning surfaces.
30
+ The AI handles the entire workflow through 279 tools spanning the supported ExtendScript, QE DOM, local media analysis, and safe edit-planning surfaces.
29
31
 
30
32
  ### What's new in 1.3.1
31
33
 
@@ -49,6 +51,14 @@ The AI handles the entire workflow through 278 tools spanning the supported Exte
49
51
 
50
52
  ## Quick Start
51
53
 
54
+ ### Before you begin
55
+
56
+ - Node.js **20.19 or newer** on Windows or macOS.
57
+ - Adobe Premiere Pro **2020–2026**. Keep Premiere, the CEP bridge, and your MCP client on the same computer for the recommended local setup.
58
+ - Optional: [ffmpeg](https://ffmpeg.org/download.html) on `PATH` for `detect_silence`
59
+ (`brew install ffmpeg` on macOS or `winget install Gyan.FFmpeg` on Windows).
60
+ The production Docker image already includes it.
61
+
52
62
  ### 1. Install
53
63
 
54
64
  **Option A — npm (recommended):**
@@ -138,6 +148,20 @@ done
138
148
 
139
149
  ### 3. Configure your MCP client
140
150
 
151
+ If you installed from npm, configure the client to run the global command:
152
+
153
+ ```json
154
+ {
155
+ "mcpServers": {
156
+ "premiere-pro": {
157
+ "command": "premiere-pro-mcp"
158
+ }
159
+ }
160
+ }
161
+ ```
162
+
163
+ If you cloned the repository instead, use the source-build configuration shown below for your client.
164
+
141
165
  <details>
142
166
  <summary><strong>Claude Desktop</strong></summary>
143
167
 
@@ -213,7 +237,8 @@ Add to your VS Code MCP server configuration:
213
237
  1. Open (or restart) Premiere Pro
214
238
  2. The bridge starts automatically using the default temp directory (or its previously saved setting)
215
239
  3. Optionally go to **Window > Extensions > MCP Bridge** to confirm the green "Running" status or change the **Temp Directory** to match your MCP client config
216
- 4. Ask your AI assistant: *"What's my current Premiere Pro project?"*
240
+ 4. Ask your AI assistant to run `get_capabilities`, then `ping`, with Premiere open.
241
+ 5. For a safe first request, ask: *"What is my current Premiere Pro project and active sequence? Do not make changes."*
217
242
 
218
243
  The default bridge directory is derived from the operating system on both sides, so most local setups should not set `PREMIERE_TEMP_DIR`. If you override it, use the same absolute path in the MCP server and CEP panel; Windows and macOS paths are not interchangeable.
219
244
 
@@ -276,9 +301,9 @@ installed separately.
276
301
  | CEP production bridge | Premiere Pro 2020–2026 | Premiere Pro 2020–2026 | Run `get_capabilities`, then `ping` with Premiere open |
277
302
  | UXP preview bridge | Premiere Pro 25.6+ | Premiere Pro 25.6+ | Live loopback WebSocket and host API verification required |
278
303
  | npm CEP installer | Copies plugin and verifies `REG_SZ` debug keys | Copies plugin and verifies the installed manifest/debug settings | Restart Premiere after installation |
279
- | CI build and unit tests | Node 18 and 22 | Node 18 and 22 | GitHub-hosted OS runners; no Adobe host is available in CI |
304
+ | CI build and unit tests | Node 20, 22, and 24 | Node 20, 22, and 24 | GitHub-hosted OS runners; no Adobe host is available in CI |
280
305
 
281
- `get_capabilities` reports the current operating system, temp directory, CEP/UXP coverage, enabled authority profile, and any live-host verification still required. It also includes a `tools` catalog generated from the tools actually registered by the server. Every entry identifies:
306
+ `get_capabilities` reports the current operating system, temp directory, CEP/UXP coverage, enabled authority profile, and any live-host verification still required. It also includes the full `tools` catalog generated from the tools registered by the server, including tools disabled by the active profile. Every entry identifies:
282
307
 
283
308
  - the execution backend (`local`, CEP/ExtendScript, QE, or orchestrator);
284
309
  - static support status (`supported`, `limited`, `experimental`, or `unsupported`);
@@ -289,6 +314,18 @@ installed separately.
289
314
 
290
315
  QE-backed tools are reported as `experimental` because QE is undocumented and can vary between Premiere builds. Authority availability is reported separately from implementation support, so disabling `edit`, for example, does not incorrectly label editing tools as unsupported. Static metadata never claims that a Premiere operation succeeded; use `ping` and inspect each tool result for runtime evidence.
291
316
 
317
+ MCP `tools/list` is filtered to the active authority profile. The default
318
+ `inspect,edit,export,filesystem` profile advertises 277 of the 279 registered
319
+ tools and omits `execute_extendscript` and `evaluate_expression`, which require
320
+ explicit `unsafe-script` authority. `ping` and `get_capabilities` remain visible
321
+ under every profile so a restricted or misconfigured server can still explain
322
+ its state. The call-time capability guard remains authoritative even if listing
323
+ metadata is wrong.
324
+
325
+ The MCP handshake reads `serverInfo.version` from the installed `package.json`,
326
+ so clients receive the package version that is actually running rather than a
327
+ separately maintained literal.
328
+
292
329
  Tools with mixed execution boundaries can provide explicit operational metadata at registration. This is used for local file verification, static feature-support reports, and hybrid local-plus-Premiere validation so the capability catalog does not infer a host dependency from naming alone.
293
330
 
294
331
  ### Collaboration and AI feature boundaries
@@ -373,7 +410,7 @@ The file-based IPC bridge is simple, reliable, and works across macOS and Window
373
410
 
374
411
  ---
375
412
 
376
- ## Tools (278)
413
+ ## Tools (279 total; 277 under the default profile)
377
414
 
378
415
  ### Discovery & Inspection (10 + 10)
379
416
 
@@ -507,13 +544,12 @@ than presenting UI-only operations as available tools.
507
544
  | `get_workspaces` / `set_workspace` | Switch workspace layouts |
508
545
  | `create_caption_track` | Create caption/subtitle tracks |
509
546
 
510
- ### Scripting (6)
547
+ ### Scripting (2)
511
548
 
512
549
  | Tool | Description |
513
550
  | :--- | :----------- |
514
- | `execute_extendscript` | Run arbitrary ExtendScript (ES3) |
515
- | `evaluate_expression` | Quick one-line eval |
516
- | `send_raw_script` | Bypass security validation (advanced) |
551
+ | `execute_extendscript` | Run arbitrary ExtendScript (ES3); requires explicit `unsafe-script` authority |
552
+ | `evaluate_expression` | Evaluate a one-line expression; requires explicit `unsafe-script` authority |
517
553
 
518
554
  ### ...and 100+ more
519
555
 
@@ -581,6 +617,7 @@ Then connect with:
581
617
  ```
582
618
 
583
619
  > **Note:** The file bridge still requires the CEP plugin to share the same `PREMIERE_TEMP_DIR`. For cloud deployments this means running a sync agent or using `fly proxy` / WireGuard to reach your local machine.
620
+ > `detect_silence` can analyze only media paths available inside the server filesystem; a desktop-only path is not automatically available to a remote Fly machine.
584
621
 
585
622
  ---
586
623
 
@@ -596,6 +633,16 @@ Then connect with:
596
633
  | `PORT` | HTTP port (HTTP/SSE transport only) | `3000` |
597
634
  | `MCP_AUTH_TOKEN` | Bearer token required by the HTTP transport | unset |
598
635
  | `ALLOW_UNAUTHENTICATED` | Set to `1` to run HTTP without auth (unsafe; throwaway instances only) | unset |
636
+ | `POSTHOG_API_KEY` | PostHog project token; enables privacy-safe MCP usage telemetry | unset |
637
+ | `POSTHOG_HOST` | PostHog ingestion host | `https://us.i.posthog.com` |
638
+ | `POSTHOG_ENVIRONMENT` | Environment property attached to telemetry events | `production` |
639
+ | `POSTHOG_DISTINCT_ID` | Optional stable anonymous server identifier | Fly machine ID or random boot ID |
640
+
641
+ When PostHog is enabled, the server records `mcp_connection_attempt`,
642
+ `mcp_request`, and `mcp_tool_call`. Events contain operational fields such as
643
+ method, tool name, outcome, status code, and duration. Authentication tokens,
644
+ IP addresses, MCP arguments, project paths, media names, and tool results are
645
+ never sent. Person profiles are disabled for these events.
599
646
 
600
647
  ---
601
648
 
@@ -606,7 +653,7 @@ premiere-pro-mcp/
606
653
  ├── src/
607
654
  │ ├── index.ts # Entry point — stdio transport setup
608
655
  │ ├── http-server.ts # Entry point — HTTP/SSE transport (Fly.io / remote)
609
- │ ├── server.ts # MCP server — registers 278 tools + 3 resources + 4 prompts
656
+ │ ├── server.ts # MCP server — registers 279 tools, filtered by authority profile
610
657
  │ ├── bridge/
611
658
  │ │ ├── file-bridge.ts # File-based IPC (write .jsx, poll .json)
612
659
  │ │ └── script-builder.ts # ExtendScript generator with ES3 helpers
@@ -619,7 +666,7 @@ premiere-pro-mcp/
619
666
  │ │ ├── timeline.ts # Timeline clip operations
620
667
  │ │ ├── effects.ts # Effect application and color correction
621
668
  │ │ ├── transitions.ts # Transition management (QE DOM)
622
- │ │ ├── audio.ts # Audio levels and keyframes
669
+ │ │ ├── audio.ts # Audio levels, keyframes, and ffmpeg silence analysis
623
670
  │ │ ├── av-settings.ts # Documented AV inspection, mapping, and capability boundaries
624
671
  │ │ ├── text.ts # Text overlays and MOGRTs
625
672
  │ │ ├── markers.ts # Sequence and clip markers
@@ -675,8 +722,9 @@ All generated scripts use **ES3 syntax** (`var`, manual `for` loops, no arrow fu
675
722
  ### Security
676
723
 
677
724
  Understand the trust model before deploying this: **any client that can reach the MCP
678
- server can control Premiere Pro.** `execute_extendscript` and `send_raw_script` are
679
- arbitrary-code-execution tools by design and are disabled by default. Enable them only by setting
725
+ server can control Premiere Pro.** `execute_extendscript` and `evaluate_expression` are
726
+ arbitrary-code-execution tools by design and are omitted from discovery and denied at call time
727
+ by default. Enable them only by setting
680
728
  `PREMIERE_MCP_CAPABILITIES=inspect,edit,export,filesystem,unsafe-script`.
681
729
 
682
730
  - **Run it locally over stdio** unless you have a specific reason not to. That's the safe default.
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.4.0" ExtensionBundleName="MCP Bridge">
2
+ <ExtensionManifest Version="7.0" ExtensionBundleId="com.mcp.premiere.bridge" ExtensionBundleVersion="1.5.0" ExtensionBundleName="MCP Bridge">
3
3
  <ExtensionList>
4
- <Extension Id="com.mcp.premiere.bridge.panel" Version="1.4.0"/>
5
- <Extension Id="com.mcp.premiere.bridge.headless" Version="1.4.0"/>
4
+ <Extension Id="com.mcp.premiere.bridge.panel" Version="1.5.0"/>
5
+ <Extension Id="com.mcp.premiere.bridge.headless" Version="1.5.0"/>
6
6
  </ExtensionList>
7
7
  <ExecutionEnvironment>
8
8
  <HostList>
@@ -65,7 +65,7 @@
65
65
  <section class="update-section" aria-live="polite">
66
66
  <div class="update-copy">
67
67
  <span class="section-label">Connector updates</span>
68
- <strong id="updateTitle">Version 1.4.0</strong>
68
+ <strong id="updateTitle">Version 1.5.0</strong>
69
69
  <span id="updateDetail">Checking for updates…</span>
70
70
  </div>
71
71
  <button id="btnUpdate" class="button button-update" onclick="handleUpdateClick()" type="button" disabled>
@@ -7,7 +7,7 @@
7
7
  })(this, function () {
8
8
  "use strict";
9
9
 
10
- var CURRENT_VERSION = "1.4.0";
10
+ var CURRENT_VERSION = "1.5.0";
11
11
  var LATEST_RELEASE_API =
12
12
  "https://api.github.com/repos/leancoderkavy/premiere-pro-mcp/releases/latest";
13
13
  var RELEASES_URL =
@@ -28,6 +28,7 @@ import { fileURLToPath } from "node:url";
28
28
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
29
29
  import { createServer } from "./server.js";
30
30
  import { cleanupTempDir, getTempDir } from "./bridge/file-bridge.js";
31
+ import { getTelemetry } from "./telemetry.js";
31
32
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
32
33
  const LANDING_DIR = path.resolve(__dirname, "../landing-dist");
33
34
  const MIME = {
@@ -36,6 +37,7 @@ const MIME = {
36
37
  ".css": "text/css; charset=utf-8",
37
38
  ".json": "application/json",
38
39
  ".png": "image/png",
40
+ ".mp4": "video/mp4",
39
41
  ".svg": "image/svg+xml",
40
42
  ".ico": "image/x-icon",
41
43
  ".woff2": "font/woff2",
@@ -96,6 +98,8 @@ const bridgeOptions = {
96
98
  ? parseInt(process.env.PREMIERE_TIMEOUT_MS, 10)
97
99
  : undefined,
98
100
  };
101
+ process.env.PREMIERE_MCP_TRANSPORT = "http";
102
+ const telemetry = getTelemetry();
99
103
  const tempDir = getTempDir(bridgeOptions);
100
104
  console.error(`[premiere-pro-mcp] Starting HTTP server on port ${PORT}...`);
101
105
  console.error(`[premiere-pro-mcp] Temp directory: ${tempDir}`);
@@ -118,11 +122,20 @@ const httpServer = http.createServer(async (req, res) => {
118
122
  }
119
123
  // Bearer token auth (constant-time; required unless ALLOW_UNAUTHENTICATED=1)
120
124
  if (!isAuthorized(req)) {
125
+ telemetry.capture("mcp_connection_attempt", {
126
+ outcome: "unauthorized",
127
+ method: req.method ?? "unknown",
128
+ });
121
129
  res.writeHead(401, { "Content-Type": "application/json" });
122
130
  res.end(JSON.stringify({ error: "Unauthorized" }));
123
131
  return;
124
132
  }
125
- const mcpServer = createServer(bridgeOptions);
133
+ const requestStartedAt = Date.now();
134
+ telemetry.capture("mcp_connection_attempt", {
135
+ outcome: "authorized",
136
+ method: req.method ?? "unknown",
137
+ });
138
+ const mcpServer = createServer(bridgeOptions, { telemetry });
126
139
  const transport = new StreamableHTTPServerTransport({
127
140
  sessionIdGenerator: undefined, // stateless
128
141
  });
@@ -133,8 +146,21 @@ const httpServer = http.createServer(async (req, res) => {
133
146
  try {
134
147
  await mcpServer.connect(transport);
135
148
  await transport.handleRequest(req, res);
149
+ telemetry.capture("mcp_request", {
150
+ outcome: res.statusCode >= 400 ? "failed" : "succeeded",
151
+ method: req.method ?? "unknown",
152
+ status_code: res.statusCode,
153
+ duration_ms: Date.now() - requestStartedAt,
154
+ });
136
155
  }
137
156
  catch (err) {
157
+ telemetry.capture("mcp_request", {
158
+ outcome: "failed",
159
+ method: req.method ?? "unknown",
160
+ status_code: 500,
161
+ duration_ms: Date.now() - requestStartedAt,
162
+ error_type: err instanceof Error ? err.name : "UnknownError",
163
+ });
138
164
  console.error("[premiere-pro-mcp] Request error:", err);
139
165
  if (!res.headersSent) {
140
166
  res.writeHead(500, { "Content-Type": "application/json" });
@@ -152,12 +178,12 @@ httpServer.listen(PORT, "0.0.0.0", () => {
152
178
  console.error(`[premiere-pro-mcp] Auth: DISABLED via ALLOW_UNAUTHENTICATED — anyone can drive this server`);
153
179
  }
154
180
  });
155
- process.on("SIGTERM", () => {
156
- console.error("[premiere-pro-mcp] SIGTERM received, shutting down...");
157
- httpServer.close(() => process.exit(0));
158
- });
159
- process.on("SIGINT", () => {
160
- console.error("[premiere-pro-mcp] SIGINT received, shutting down...");
161
- httpServer.close(() => process.exit(0));
162
- });
181
+ async function shutdown(signal) {
182
+ console.error(`[premiere-pro-mcp] ${signal} received, shutting down...`);
183
+ httpServer.close();
184
+ await telemetry.shutdown();
185
+ process.exit(0);
186
+ }
187
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
188
+ process.on("SIGINT", () => void shutdown("SIGINT"));
163
189
  //# sourceMappingURL=http-server.js.map
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
3
  import { createServer } from "./server.js";
4
4
  import { cleanupTempDir, getTempDir } from "./bridge/file-bridge.js";
5
+ import { getTelemetry } from "./telemetry.js";
5
6
  import { execFileSync } from "child_process";
6
7
  import { fileURLToPath } from "url";
7
8
  import path from "path";
@@ -19,7 +20,7 @@ function debugLog(message) {
19
20
  const args = process.argv.slice(2);
20
21
  if (args.includes("--help") || args.includes("-h")) {
21
22
  console.log(`
22
- premiere-pro-mcp — MCP server for Adobe Premiere Pro (278 tools)
23
+ premiere-pro-mcp — MCP server for Adobe Premiere Pro (279 tools)
23
24
 
24
25
  Usage:
25
26
  premiere-pro-mcp Start the MCP server (stdio transport)
@@ -79,6 +80,8 @@ if (args.includes("--install-cep") || args.includes("--diagnose-cep")) {
79
80
  process.exit(0);
80
81
  }
81
82
  async function main() {
83
+ process.env.PREMIERE_MCP_TRANSPORT = "stdio";
84
+ const telemetry = getTelemetry();
82
85
  const bridgeOptions = {
83
86
  tempDir: process.env.PREMIERE_TEMP_DIR,
84
87
  timeoutMs: process.env.PREMIERE_TIMEOUT_MS
@@ -102,7 +105,7 @@ async function main() {
102
105
  const address = uxpBridge.address();
103
106
  debugLog(`UXP bridge listening on ws://${address.host}:${address.port}${address.path}`);
104
107
  }
105
- const server = createServer(bridgeOptions, { uxpBridge });
108
+ const server = createServer(bridgeOptions, { uxpBridge, telemetry });
106
109
  const transport = new StdioServerTransport();
107
110
  await server.connect(transport);
108
111
  debugLog("Server connected and ready");
@@ -110,6 +113,7 @@ async function main() {
110
113
  if (uxpBridge)
111
114
  await uxpBridge.stop();
112
115
  await server.close();
116
+ await telemetry.shutdown();
113
117
  };
114
118
  process.once("SIGINT", () => void shutdown().finally(() => process.exit(0)));
115
119
  process.once("SIGTERM", () => void shutdown().finally(() => process.exit(0)));
@@ -19,5 +19,20 @@ export declare function requireCapability(config: CapabilityConfig, capability:
19
19
  export declare const UNSAFE_TOOL_NAMES: Set<string>;
20
20
  /** A conservative classification for centralized server registration. */
21
21
  export declare function capabilityForTool(toolName: string): Capability;
22
+ /**
23
+ * Tools that stay advertised under every profile, because they are how an
24
+ * operator diagnoses a profile that is too narrow: get_capabilities reports the
25
+ * active authority, and ping proves the bridge is alive. Withholding them makes
26
+ * a misconfigured server look broken with no supported way to ask why.
27
+ */
28
+ export declare const ALWAYS_LISTED_TOOL_NAMES: Set<string>;
29
+ /**
30
+ * Whether a tool should appear in tools/list under the given profile.
31
+ *
32
+ * This is an advertising decision, not an enforcement one — guardToolHandler
33
+ * remains the authority check at call time. Keeping the two separate means a
34
+ * listing bug can never widen what a profile is actually allowed to do.
35
+ */
36
+ export declare function isToolPermitted(toolName: string, config: CapabilityConfig): boolean;
22
37
  export declare function guardToolHandler<TArgs, TResult>(toolName: string, handler: (args: TArgs) => Promise<TResult>, config?: CapabilityConfig, createOperationId?: () => string): (args: TArgs) => Promise<TResult>;
23
38
  //# sourceMappingURL=capabilities.d.ts.map
@@ -39,7 +39,10 @@ export function requireCapability(config, capability, operationId) {
39
39
  }
40
40
  export const UNSAFE_TOOL_NAMES = new Set(["execute_extendscript", "send_raw_script", "evaluate_expression"]);
41
41
  const INSPECT_TOOL_NAMES = new Set(["ping", "get_capabilities", "preview_edit_plan"]);
42
- const FILESYSTEM_TOOL_NAMES = new Set(["apply_lut", "set_scratch_disk_path", "verify_delivery_file"]);
42
+ // detect_silence reads a media file from disk and shells out to ffmpeg. It
43
+ // changes nothing in Premiere, so classifying it as "edit" would overstate what
44
+ // it does; filesystem is the authority it actually needs.
45
+ const FILESYSTEM_TOOL_NAMES = new Set(["apply_lut", "set_scratch_disk_path", "verify_delivery_file", "detect_silence"]);
43
46
  /** A conservative classification for centralized server registration. */
44
47
  export function capabilityForTool(toolName) {
45
48
  if (UNSAFE_TOOL_NAMES.has(toolName))
@@ -56,6 +59,25 @@ export function capabilityForTool(toolName) {
56
59
  // keeps new tools fail-closed instead of silently bypassing the authority profile.
57
60
  return "edit";
58
61
  }
62
+ /**
63
+ * Tools that stay advertised under every profile, because they are how an
64
+ * operator diagnoses a profile that is too narrow: get_capabilities reports the
65
+ * active authority, and ping proves the bridge is alive. Withholding them makes
66
+ * a misconfigured server look broken with no supported way to ask why.
67
+ */
68
+ export const ALWAYS_LISTED_TOOL_NAMES = new Set(["ping", "get_capabilities"]);
69
+ /**
70
+ * Whether a tool should appear in tools/list under the given profile.
71
+ *
72
+ * This is an advertising decision, not an enforcement one — guardToolHandler
73
+ * remains the authority check at call time. Keeping the two separate means a
74
+ * listing bug can never widen what a profile is actually allowed to do.
75
+ */
76
+ export function isToolPermitted(toolName, config) {
77
+ if (ALWAYS_LISTED_TOOL_NAMES.has(toolName))
78
+ return true;
79
+ return config.capabilities.has(capabilityForTool(toolName));
80
+ }
59
81
  export function guardToolHandler(toolName, handler, config = resolveCapabilities(), createOperationId = randomUUID) {
60
82
  const required = capabilityForTool(toolName);
61
83
  return async (args) => {
package/dist/server.d.ts CHANGED
@@ -1,8 +1,16 @@
1
1
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { BridgeOptions } from "./bridge/file-bridge.js";
3
3
  import type { UxpWebSocketBridge } from "./bridge/uxp-websocket-bridge.js";
4
+ import { type Telemetry } from "./telemetry.js";
5
+ /**
6
+ * Read the version from package.json rather than hardcoding it. A literal here
7
+ * silently drifts from the published package every release, which makes the
8
+ * version reported over MCP useless for triaging bug reports.
9
+ */
10
+ export declare const SERVER_VERSION: string;
4
11
  export interface ServerOptions {
5
12
  uxpBridge?: UxpWebSocketBridge;
13
+ telemetry?: Telemetry;
6
14
  }
7
15
  export declare function createServer(bridgeOptions: BridgeOptions, serverOptions?: ServerOptions): McpServer;
8
16
  //# sourceMappingURL=server.d.ts.map
package/dist/server.js CHANGED
@@ -31,11 +31,31 @@ import { getEditPlanTools } from "./tools/edit-plans.js";
31
31
  import { getAvSettingsTools } from "./tools/av-settings.js";
32
32
  import { getRecoveryTools } from "./tools/recovery.js";
33
33
  import { getUxpTools } from "./tools/uxp.js";
34
- import { guardToolHandler, resolveCapabilities } from "./security/index.js";
34
+ import { guardToolHandler, isToolPermitted, resolveCapabilities, } from "./security/index.js";
35
35
  import { EXTENDSCRIPT_REFERENCE } from "./resources/extendscript-reference.js";
36
36
  import { WORKFLOW_PROMPTS, WORKFLOW_RESOURCE } from "./workflows/catalog.js";
37
37
  import { annotationsForTool, structuredToolResult, } from "./workflows/tool-metadata.js";
38
+ import { getTelemetry } from "./telemetry.js";
38
39
  import { z } from "zod";
40
+ import { readFileSync } from "node:fs";
41
+ import { fileURLToPath } from "node:url";
42
+ import path from "node:path";
43
+ /**
44
+ * Read the version from package.json rather than hardcoding it. A literal here
45
+ * silently drifts from the published package every release, which makes the
46
+ * version reported over MCP useless for triaging bug reports.
47
+ */
48
+ export const SERVER_VERSION = (() => {
49
+ try {
50
+ const here = path.dirname(fileURLToPath(import.meta.url));
51
+ const raw = readFileSync(path.resolve(here, "../package.json"), "utf8");
52
+ const version = JSON.parse(raw).version;
53
+ return typeof version === "string" && version ? version : "unknown";
54
+ }
55
+ catch {
56
+ return "unknown";
57
+ }
58
+ })();
39
59
  const PREMIERE_INSTRUCTIONS = `You are controlling Adobe Premiere Pro through MCP tools. Follow these best practices:
40
60
 
41
61
  WORKFLOW ORDER:
@@ -222,18 +242,34 @@ function collectTools(bridgeOptions, capabilities, uxpBridge) {
222
242
  export function createServer(bridgeOptions, serverOptions = {}) {
223
243
  const server = new McpServer({
224
244
  name: "premiere-pro-mcp",
225
- version: "1.2.0",
245
+ version: SERVER_VERSION,
226
246
  });
227
247
  const capabilities = resolveCapabilities();
248
+ const telemetry = serverOptions.telemetry ?? getTelemetry();
228
249
  // Collect all tools from each module
229
250
  const toolModules = collectTools(bridgeOptions, capabilities, serverOptions.uxpBridge);
230
251
  // Register each tool with the MCP server
252
+ let withheld = 0;
231
253
  for (const [name, tool] of Object.entries(toolModules)) {
254
+ // Don't advertise tools the active authority profile will always refuse.
255
+ // guardToolHandler still rejects them at call time, but listing an
256
+ // unusable tool spends client context and invites the model to attempt a
257
+ // call that cannot succeed.
258
+ if (!isToolPermitted(name, capabilities)) {
259
+ withheld++;
260
+ continue;
261
+ }
232
262
  const zodShape = jsonSchemaToZodShape(tool.parameters);
233
263
  const guardedHandler = guardToolHandler(name, tool.handler, capabilities);
234
264
  server.tool(name, tool.description, zodShape, annotationsForTool(name), async (args) => {
265
+ const startedAt = Date.now();
235
266
  try {
236
267
  const result = await guardedHandler(args);
268
+ telemetry.capture("mcp_tool_call", {
269
+ tool: name,
270
+ outcome: result.success ? "succeeded" : "failed",
271
+ duration_ms: Date.now() - startedAt,
272
+ });
237
273
  if (result.success) {
238
274
  // Special handling for capture_frame: return image content block
239
275
  const data = result.data;
@@ -281,6 +317,12 @@ export function createServer(bridgeOptions, serverOptions = {}) {
281
317
  }
282
318
  }
283
319
  catch (err) {
320
+ telemetry.capture("mcp_tool_call", {
321
+ tool: name,
322
+ outcome: "failed",
323
+ duration_ms: Date.now() - startedAt,
324
+ error_type: err instanceof Error ? err.name : "UnknownError",
325
+ });
284
326
  return {
285
327
  structuredContent: structuredToolResult(name, false, undefined, err instanceof Error ? err.message : String(err)),
286
328
  content: [
@@ -294,6 +336,11 @@ export function createServer(bridgeOptions, serverOptions = {}) {
294
336
  }
295
337
  });
296
338
  }
339
+ if (withheld > 0) {
340
+ debugLog(`Withheld ${withheld} tool(s) from tools/list — not permitted by the ` +
341
+ `active capability profile (${[...capabilities.capabilities].sort().join(", ")}). ` +
342
+ `Call get_capabilities to see the enabled authority.`);
343
+ }
297
344
  // Register LLM instructions resource
298
345
  server.resource("premiere-instructions", "config://premiere-instructions", {
299
346
  description: "Instructions and best practices for using Premiere Pro via MCP tools",
@@ -0,0 +1,8 @@
1
+ export type TelemetryProperties = Record<string, string | number | boolean | null | undefined>;
2
+ export interface Telemetry {
3
+ readonly enabled: boolean;
4
+ capture(event: string, properties?: TelemetryProperties): void;
5
+ shutdown(): Promise<void>;
6
+ }
7
+ export declare function getTelemetry(): Telemetry;
8
+ //# sourceMappingURL=telemetry.d.ts.map
@@ -0,0 +1,63 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { PostHog } from "posthog-node";
3
+ class PostHogTelemetry {
4
+ client;
5
+ distinctId;
6
+ commonProperties;
7
+ enabled = true;
8
+ constructor(client, distinctId, commonProperties) {
9
+ this.client = client;
10
+ this.distinctId = distinctId;
11
+ this.commonProperties = commonProperties;
12
+ }
13
+ capture(event, properties = {}) {
14
+ this.client.capture({
15
+ distinctId: this.distinctId,
16
+ event,
17
+ properties: {
18
+ ...this.commonProperties,
19
+ ...properties,
20
+ $process_person_profile: false,
21
+ },
22
+ });
23
+ }
24
+ async shutdown() {
25
+ await this.client.shutdown();
26
+ }
27
+ }
28
+ const disabledTelemetry = {
29
+ enabled: false,
30
+ capture: () => { },
31
+ shutdown: async () => { },
32
+ };
33
+ let telemetry;
34
+ export function getTelemetry() {
35
+ if (telemetry)
36
+ return telemetry;
37
+ const apiKey = process.env.POSTHOG_API_KEY;
38
+ if (!apiKey) {
39
+ telemetry = disabledTelemetry;
40
+ return telemetry;
41
+ }
42
+ const host = process.env.POSTHOG_HOST ?? "https://us.i.posthog.com";
43
+ const distinctId = process.env.POSTHOG_DISTINCT_ID ??
44
+ process.env.FLY_MACHINE_ID ??
45
+ `server-${randomUUID()}`;
46
+ const client = new PostHog(apiKey, {
47
+ host,
48
+ // MCP servers may be idle for long stretches. Flush each bounded operational
49
+ // event promptly so a low-volume deployment remains observable.
50
+ flushAt: 1,
51
+ flushInterval: 10_000,
52
+ });
53
+ telemetry = new PostHogTelemetry(client, distinctId, {
54
+ service: "premiere-pro-mcp",
55
+ service_version: process.env.npm_package_version ?? "unknown",
56
+ environment: process.env.POSTHOG_ENVIRONMENT ?? process.env.NODE_ENV ?? "production",
57
+ region: process.env.FLY_REGION,
58
+ transport: process.env.PREMIERE_MCP_TRANSPORT ?? "unknown",
59
+ });
60
+ console.error(`[premiere-pro-mcp] PostHog telemetry enabled (${host})`);
61
+ return telemetry;
62
+ }
63
+ //# sourceMappingURL=telemetry.js.map