@synthetic-ai/premiere-mcp 2.3.1-dev.1 → 2.3.4

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.
@@ -1,6 +1,7 @@
1
1
  import { mkdirSync, writeFileSync, readFileSync, unlinkSync, existsSync, readdirSync, renameSync } from "node:fs";
2
2
  import { join } from "node:path";
3
3
  import { tmpdir } from "node:os";
4
+ import { t } from "../i18n.js";
4
5
  const DEFAULT_TEMP_DIR = join(tmpdir(), "premiere-mcp-bridge");
5
6
  const POLL_INTERVAL_MS = 100;
6
7
  const DEFAULT_TIMEOUT_MS = 30000;
@@ -46,7 +47,7 @@ export async function sendCommand(script, options) {
46
47
  function validateScript(script, allowUnsafe = false) {
47
48
  const MAX_SCRIPT_SIZE = 500 * 1024; // 500KB
48
49
  if (Buffer.byteLength(script, "utf-8") > MAX_SCRIPT_SIZE) {
49
- throw new Error("Script exceeds 500KB size limit");
50
+ throw new Error(t("msg.bridge.scriptTooLarge", { limit: "500KB" }));
50
51
  }
51
52
  if (allowUnsafe)
52
53
  return;
@@ -59,7 +60,7 @@ function validateScript(script, allowUnsafe = false) {
59
60
  ];
60
61
  for (const pattern of dangerousPatterns) {
61
62
  if (pattern.test(script)) {
62
- throw new Error(`Script contains blocked pattern: ${pattern.source}`);
63
+ throw new Error(t("msg.bridge.blockedPattern", { pattern: pattern.source }));
63
64
  }
64
65
  }
65
66
  }
@@ -96,7 +97,7 @@ async function pollForResponse(resFile, timeoutMs) {
96
97
  catch (e) {
97
98
  resolve({
98
99
  success: false,
99
- error: `Failed to parse response: ${e instanceof Error ? e.message : String(e)}`,
100
+ error: t("msg.bridge.parseFailed", { error: e instanceof Error ? e.message : String(e) }),
100
101
  });
101
102
  }
102
103
  return;
@@ -104,7 +105,7 @@ async function pollForResponse(resFile, timeoutMs) {
104
105
  if (Date.now() - start > timeoutMs) {
105
106
  resolve({
106
107
  success: false,
107
- error: `Command timed out after ${timeoutMs}ms. Is the CEP plugin running in Premiere Pro?`,
108
+ error: t("msg.bridge.timeout", { ms: timeoutMs }),
108
109
  });
109
110
  return;
110
111
  }
@@ -16,6 +16,7 @@
16
16
  * PREMIERE_TEMP_DIR Shared temp directory for the file bridge
17
17
  * PREMIERE_TIMEOUT_MS Command timeout in ms (default: 30000)
18
18
  * MCP_AUTH_TOKEN Optional bearer token to require on all requests
19
+ * PREMIERE_PRO_MCP_LANG Language of tool descriptions/messages (see src/i18n.ts)
19
20
  */
20
21
  export {};
21
22
  //# sourceMappingURL=http-server.d.ts.map
@@ -16,6 +16,7 @@
16
16
  * PREMIERE_TEMP_DIR Shared temp directory for the file bridge
17
17
  * PREMIERE_TIMEOUT_MS Command timeout in ms (default: 30000)
18
18
  * MCP_AUTH_TOKEN Optional bearer token to require on all requests
19
+ * PREMIERE_PRO_MCP_LANG Language of tool descriptions/messages (see src/i18n.ts)
19
20
  */
20
21
  import http from "node:http";
21
22
  import fs from "node:fs";
@@ -24,6 +25,7 @@ import { fileURLToPath } from "node:url";
24
25
  import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
25
26
  import { createServer } from "./server.js";
26
27
  import { cleanupTempDir, getTempDir } from "./bridge/file-bridge.js";
28
+ import { logLanguage } from "./i18n.js";
27
29
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
28
30
  const LANDING_DIR = path.resolve(__dirname, "../landing-dist");
29
31
  const MIME = {
@@ -72,6 +74,7 @@ const bridgeOptions = {
72
74
  const tempDir = getTempDir(bridgeOptions);
73
75
  console.error(`[premiere-pro-mcp] Starting HTTP server on port ${PORT}...`);
74
76
  console.error(`[premiere-pro-mcp] Temp directory: ${tempDir}`);
77
+ logLanguage();
75
78
  cleanupTempDir(bridgeOptions);
76
79
  // Each request gets its own transport+server instance (stateless per-request model)
77
80
  const httpServer = http.createServer(async (req, res) => {
package/dist/i18n.d.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * i18n — language of the tool descriptions and of the messages this server
3
+ * prints or returns to the MCP client.
4
+ *
5
+ * The runtime (node.cjs + i18n-core.cjs) and the dictionaries come from the
6
+ * central synthetic-idiomas repo, vendored into <package root>/i18n by
7
+ * scripts/sync-i18n.mjs. They're loaded at runtime (not compiled in), resolved
8
+ * relative to this module: dist/i18n.js → ../i18n, and the esbuild bundle at
9
+ * release/server/index.mjs → release/i18n (scripts/package.mjs copies it).
10
+ *
11
+ * The language is picked ONCE at startup, first match wins:
12
+ * PREMIERE_PRO_MCP_LANG → SYNTHETIC_LANG →
13
+ * <USER_DATA>/com.mcp.premiere.bridge/settings.json (written by the panels'
14
+ * language selector) → <USER_DATA>/com.synthetic/settings.json → English.
15
+ * MCP clients only read tool descriptions when they connect, so a change in
16
+ * the panel applies after the MCP server (or the client app) restarts.
17
+ */
18
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
19
+ export declare const LANG_ENV_VAR = "PREMIERE_PRO_MCP_LANG";
20
+ export declare const SETTINGS_FOLDER = "com.mcp.premiere.bridge";
21
+ type Vars = Record<string, string | number | null | undefined>;
22
+ interface ServerI18n {
23
+ language: string;
24
+ languageSource: string;
25
+ t(key: string, vars?: Vars | null, fallbackText?: string): string;
26
+ }
27
+ export declare const i18n: ServerI18n;
28
+ /** Localized text for a `msg.*` key, with `{placeholders}` filled from `vars`. */
29
+ export declare function t(key: string, vars?: Vars): string;
30
+ /** Language codes supported by the dictionaries, e.g. "en, es, pt-BR, …". */
31
+ export declare function supportedLanguages(): string[];
32
+ /** Display name of the active language in its own language ("Español"). */
33
+ export declare function languageName(): string;
34
+ /**
35
+ * Serve translated tool descriptions from tools/list. Call after every tool
36
+ * is registered and before connect(). No-op in English.
37
+ */
38
+ export declare function localizeToolsList(server: McpServer): boolean;
39
+ /** One stderr line at startup: which language and where the choice came from. */
40
+ export declare function logLanguage(): void;
41
+ export {};
42
+ //# sourceMappingURL=i18n.d.ts.map
package/dist/i18n.js ADDED
@@ -0,0 +1,69 @@
1
+ /**
2
+ * i18n — language of the tool descriptions and of the messages this server
3
+ * prints or returns to the MCP client.
4
+ *
5
+ * The runtime (node.cjs + i18n-core.cjs) and the dictionaries come from the
6
+ * central synthetic-idiomas repo, vendored into <package root>/i18n by
7
+ * scripts/sync-i18n.mjs. They're loaded at runtime (not compiled in), resolved
8
+ * relative to this module: dist/i18n.js → ../i18n, and the esbuild bundle at
9
+ * release/server/index.mjs → release/i18n (scripts/package.mjs copies it).
10
+ *
11
+ * The language is picked ONCE at startup, first match wins:
12
+ * PREMIERE_PRO_MCP_LANG → SYNTHETIC_LANG →
13
+ * <USER_DATA>/com.mcp.premiere.bridge/settings.json (written by the panels'
14
+ * language selector) → <USER_DATA>/com.synthetic/settings.json → English.
15
+ * MCP clients only read tool descriptions when they connect, so a change in
16
+ * the panel applies after the MCP server (or the client app) restarts.
17
+ */
18
+ import { createRequire } from "node:module";
19
+ import { fileURLToPath } from "node:url";
20
+ import path from "node:path";
21
+ export const LANG_ENV_VAR = "PREMIERE_PRO_MCP_LANG";
22
+ export const SETTINGS_FOLDER = "com.mcp.premiere.bridge";
23
+ const I18N_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "i18n");
24
+ // A local name (not `require`) so bundlers leave this runtime load alone.
25
+ const requireCjs = createRequire(import.meta.url);
26
+ let runtime = null;
27
+ let loadError = null;
28
+ try {
29
+ runtime = requireCjs(path.join(I18N_DIR, "node.cjs"));
30
+ }
31
+ catch (err) {
32
+ loadError = (err instanceof Error ? err.message : String(err)).split("\n")[0];
33
+ }
34
+ export const i18n = runtime
35
+ ? runtime.createServerI18n({
36
+ localesDir: path.join(I18N_DIR, "locales"),
37
+ envVar: LANG_ENV_VAR,
38
+ settingsFolder: SETTINGS_FOLDER,
39
+ })
40
+ : { language: "en", languageSource: "default", t: (key) => key };
41
+ /** Localized text for a `msg.*` key, with `{placeholders}` filled from `vars`. */
42
+ export function t(key, vars) {
43
+ return i18n.t(key, vars);
44
+ }
45
+ /** Language codes supported by the dictionaries, e.g. "en, es, pt-BR, …". */
46
+ export function supportedLanguages() {
47
+ return runtime ? runtime.core.LANGUAGES.map((l) => l.code) : ["en"];
48
+ }
49
+ /** Display name of the active language in its own language ("Español"). */
50
+ export function languageName() {
51
+ const found = runtime?.core.LANGUAGES.find((l) => l.code === i18n.language);
52
+ return found ? found.name : i18n.language;
53
+ }
54
+ /**
55
+ * Serve translated tool descriptions from tools/list. Call after every tool
56
+ * is registered and before connect(). No-op in English.
57
+ */
58
+ export function localizeToolsList(server) {
59
+ return runtime ? runtime.patchMcpServerToolsList(server, i18n) : false;
60
+ }
61
+ /** One stderr line at startup: which language and where the choice came from. */
62
+ export function logLanguage() {
63
+ if (loadError) {
64
+ console.error(`[premiere-pro-mcp] i18n not loaded from ${I18N_DIR} (${loadError}) — run scripts/sync-i18n.mjs`);
65
+ return;
66
+ }
67
+ console.error(`[premiere-pro-mcp] Language: ${i18n.language} (${i18n.languageSource})`);
68
+ }
69
+ //# sourceMappingURL=i18n.js.map
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ 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
5
  import { checkLicense } from "./license.js";
6
+ import { t, i18n, languageName, logLanguage, supportedLanguages, LANG_ENV_VAR } from "./i18n.js";
6
7
  import { execSync } from "child_process";
7
8
  import { fileURLToPath } from "url";
8
9
  import path from "path";
@@ -39,7 +40,7 @@ function premiereInstallCandidates() {
39
40
  // ("UV_HANDLE_CLOSING", src/win/async.c) before the process actually exits.
40
41
  // Setting exitCode and letting the event loop drain naturally avoids it.
41
42
  async function runDoctor() {
42
- console.log(`\n${DIM}premiere-mcp doctor — checking your setup${RESET}\n`);
43
+ console.log(`\n${DIM}${t("msg.doctor.title")}${RESET}\n`);
43
44
  let hasFail = false;
44
45
  // 1. Node version
45
46
  const nodeMajor = parseInt(process.version.slice(1).split(".")[0], 10);
@@ -48,35 +49,35 @@ async function runDoctor() {
48
49
  }
49
50
  else {
50
51
  hasFail = true;
51
- printCheck("fail", `Node.js ${process.version}`, "Requires Node 18 or newer — update from nodejs.org");
52
+ printCheck("fail", `Node.js ${process.version}`, t("msg.doctor.nodeTooOld"));
52
53
  }
53
54
  // 2. OS (informational — both are supported, this just confirms detection)
54
55
  const osLabel = process.platform === "darwin" ? "macOS" : process.platform === "win32" ? "Windows" : process.platform;
55
- printCheck(process.platform === "darwin" || process.platform === "win32" ? "pass" : "warn", `OS: ${osLabel}`);
56
+ printCheck(process.platform === "darwin" || process.platform === "win32" ? "pass" : "warn", t("msg.doctor.os", { os: osLabel }));
56
57
  // 3. Premiere Pro install detected on disk
57
58
  const found = premiereInstallCandidates().find((p) => existsSync(p));
58
59
  if (found) {
59
- printCheck("pass", "Premiere Pro detected", found);
60
+ printCheck("pass", t("msg.doctor.premiereFound"), found);
60
61
  }
61
62
  else {
62
- printCheck("warn", "Premiere Pro not found in the usual install location", "Not fatal — the MCP server only needs Premiere Pro running when a tool actually calls it.");
63
+ printCheck("warn", t("msg.doctor.premiereNotFound"), t("msg.doctor.premiereNotFoundDetail"));
63
64
  }
64
65
  // 4. License key present
65
66
  const licenseKey = process.env.PREMIERE_PRO_MCP_LICENSE;
66
67
  if (!licenseKey) {
67
68
  hasFail = true;
68
- printCheck("fail", "PREMIERE_PRO_MCP_LICENSE not set", "Add it to your MCP client config (claude_desktop_config.json or equivalent).");
69
+ printCheck("fail", t("msg.doctor.licenseMissing"), t("msg.doctor.licenseMissingDetail"));
69
70
  }
70
71
  else {
71
- printCheck("pass", `License key present (${licenseKey.slice(0, 8)}...)`);
72
+ printCheck("pass", t("msg.doctor.licensePresent", { prefix: licenseKey.slice(0, 8) }));
72
73
  // 5. Live validation against synthetic.com.ar (also exercises network reachability)
73
74
  const result = await checkLicense();
74
75
  if (result.valid) {
75
- printCheck("pass", "License validates", result.expiresAt ? `Expires: ${result.expiresAt}` : "No expiration");
76
+ printCheck("pass", t("msg.doctor.licenseValid"), result.expiresAt ? t("msg.doctor.licenseExpires", { date: result.expiresAt }) : t("msg.doctor.licenseNoExpiration"));
76
77
  }
77
78
  else {
78
79
  hasFail = true;
79
- printCheck("fail", "License validation failed", result.message ?? "Unknown reason");
80
+ printCheck("fail", t("msg.doctor.licenseInvalid"), result.message ?? t("msg.doctor.unknownReason"));
80
81
  }
81
82
  }
82
83
  // 6. Bridge temp directory writable
@@ -87,36 +88,44 @@ async function runDoctor() {
87
88
  const probe = path.join(tempDir, `.doctor-probe-${Date.now()}`);
88
89
  writeFileSync(probe, "ok");
89
90
  unlinkSync(probe);
90
- printCheck("pass", "Bridge temp directory writable", tempDir);
91
+ printCheck("pass", t("msg.doctor.tempWritable"), tempDir);
91
92
  }
92
93
  catch (err) {
93
94
  hasFail = true;
94
- printCheck("fail", "Bridge temp directory not writable", `${tempDir} — ${err instanceof Error ? err.message : String(err)}`);
95
+ printCheck("fail", t("msg.doctor.tempNotWritable"), `${tempDir} — ${err instanceof Error ? err.message : String(err)}`);
95
96
  }
97
+ // 7. Language of tool descriptions + messages (informational)
98
+ printCheck("pass", t("msg.doctor.language", { language: languageName() }), i18n.languageSource === "default"
99
+ ? t("msg.doctor.languageDefault")
100
+ : t("msg.doctor.languageFrom", { source: i18n.languageSource }));
96
101
  console.log(hasFail
97
- ? `\n${RED}Some checks failed.${RESET} Fix the items above, or share this output with support at https://synthetic.com.ar/dashboard/support\n`
98
- : `\n${GREEN}Everything looks good.${RESET}\n`);
102
+ ? `\n${RED}${t("msg.doctor.someFailed")}${RESET} ${t("msg.doctor.someFailedHint")}\n`
103
+ : `\n${GREEN}${t("msg.doctor.allGood")}${RESET}\n`);
99
104
  return hasFail;
100
105
  }
101
106
  // Handle CLI flags
102
107
  const args = process.argv.slice(2);
103
108
  if (args.includes("--help") || args.includes("-h")) {
104
- console.log(`
105
- premiere-pro-mcp — MCP server for Adobe Premiere Pro (437 tools)
106
-
107
- Usage:
108
- premiere-pro-mcp Start the MCP server (stdio transport)
109
- premiere-pro-mcp --doctor Check your setup (Node, license, Premiere Pro, temp dir)
110
- premiere-pro-mcp --install-cep Install the CEP plugin into Premiere Pro
111
- premiere-pro-mcp --help Show this help message
112
- premiere-pro-mcp --version Show version
113
-
114
- Environment variables:
115
- PREMIERE_TEMP_DIR Shared temp directory (default: OS temp + /premiere-mcp-bridge)
116
- PREMIERE_TIMEOUT_MS Command timeout in ms (default: 30000)
117
-
118
- More info: https://synthetic.com.ar
119
- `);
109
+ const row = (cmd, text, width) => ` ${cmd.padEnd(width)} ${text}`;
110
+ console.log([
111
+ "",
112
+ `premiere-pro-mcp — ${t("msg.help.title", { count: 437 })}`,
113
+ "",
114
+ t("msg.help.usage"),
115
+ row("premiere-pro-mcp", t("msg.help.start"), 31),
116
+ row("premiere-pro-mcp --doctor", t("msg.help.doctor"), 31),
117
+ row("premiere-pro-mcp --install-cep", t("msg.help.installCep"), 31),
118
+ row("premiere-pro-mcp --help", t("msg.help.help"), 31),
119
+ row("premiere-pro-mcp --version", t("msg.help.version"), 31),
120
+ "",
121
+ t("msg.help.env"),
122
+ row("PREMIERE_TEMP_DIR", t("msg.help.envTempDir"), 22),
123
+ row("PREMIERE_TIMEOUT_MS", t("msg.help.envTimeout"), 22),
124
+ row(LANG_ENV_VAR, t("msg.help.envLang", { languages: supportedLanguages().join(", ") }), 22),
125
+ "",
126
+ t("msg.help.moreInfo", { url: "https://synthetic.com.ar" }),
127
+ "",
128
+ ].join("\n"));
120
129
  process.exit(0);
121
130
  }
122
131
  if (args.includes("--version") || args.includes("-v")) {
@@ -132,12 +141,12 @@ if (args.includes("--doctor")) {
132
141
  }
133
142
  if (args.includes("--install-cep")) {
134
143
  const scriptPath = path.join(projectRoot, "scripts", "install-cep.sh");
135
- console.log("Installing CEP plugin...\n");
144
+ console.log(`${t("msg.installCep.installing")}\n`);
136
145
  try {
137
146
  execSync(`bash "${scriptPath}"`, { stdio: "inherit", cwd: projectRoot });
138
147
  }
139
148
  catch {
140
- console.error("CEP installation failed. Try running manually:");
149
+ console.error(t("msg.installCep.failed"));
141
150
  console.error(` bash "${scriptPath}"`);
142
151
  process.exit(1);
143
152
  }
@@ -153,6 +162,7 @@ async function main() {
153
162
  const tempDir = getTempDir(bridgeOptions);
154
163
  console.error(`[premiere-pro-mcp] Starting MCP server...`);
155
164
  console.error(`[premiere-pro-mcp] Temp directory: ${tempDir}`);
165
+ logLanguage();
156
166
  // Clean up any stale files from previous sessions
157
167
  cleanupTempDir(bridgeOptions);
158
168
  const server = createServer(bridgeOptions);
package/dist/license.js CHANGED
@@ -7,6 +7,7 @@ import os from "node:os";
7
7
  import fs from "node:fs";
8
8
  import path from "node:path";
9
9
  import crypto from "node:crypto";
10
+ import { t } from "./i18n.js";
10
11
  const LICENSE_KEY = process.env.PREMIERE_PRO_MCP_LICENSE;
11
12
  const LICENSE_VALIDATION_URL = "https://synthetic.com.ar/api/v1/license/validate";
12
13
  const LICENSE_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
@@ -68,7 +69,7 @@ export async function checkLicense() {
68
69
  cache = {
69
70
  valid: false,
70
71
  plan: null,
71
- message: "No license key. Set PREMIERE_PRO_MCP_LICENSE in your MCP config. Get a license at https://synthetic.com.ar/premiere-mcp",
72
+ message: t("msg.license.missing"),
72
73
  checkedAt: Date.now(),
73
74
  };
74
75
  return cache;
@@ -91,7 +92,7 @@ export async function checkLicense() {
91
92
  else {
92
93
  cache = {
93
94
  valid: false,
94
- message: "License server unreachable. Check your internet connection.",
95
+ message: t("msg.license.unreachable"),
95
96
  checkedAt: Date.now(),
96
97
  };
97
98
  }
@@ -99,23 +100,24 @@ export async function checkLicense() {
99
100
  catch {
100
101
  cache = {
101
102
  valid: false,
102
- message: "License server unreachable. Check your internet connection.",
103
+ message: t("msg.license.unreachable"),
103
104
  checkedAt: Date.now(),
104
105
  };
105
106
  }
106
107
  return cache;
107
108
  }
108
109
  export function licenseErrorResponse(toolName) {
109
- const msg = cache?.message ?? "Invalid or missing license.";
110
+ // cache.message may come from the license server as-is (not translated here).
111
+ const msg = cache?.message ?? t("msg.license.invalid");
110
112
  return {
111
113
  content: [
112
114
  {
113
115
  type: "text",
114
116
  text: JSON.stringify({
115
- error: "License required",
117
+ error: t("msg.license.required"),
116
118
  detail: msg,
117
119
  tool: toolName,
118
- info: "Get a license at https://synthetic.com.ar/premiere-mcp",
120
+ info: t("msg.license.getLicense"),
119
121
  }),
120
122
  },
121
123
  ],
package/dist/server.js CHANGED
@@ -37,6 +37,7 @@ import { getExportPresetsTools } from "./tools/export-presets.js";
37
37
  import { getSequenceSettingsTools } from "./tools/sequence-settings.js";
38
38
  import { EXTENDSCRIPT_REFERENCE } from "./resources/extendscript-reference.js";
39
39
  import { checkLicense, licenseErrorResponse, FREE_TOOLS } from "./license.js";
40
+ import { t, localizeToolsList } from "./i18n.js";
40
41
  import { z } from "zod";
41
42
  const PREMIERE_INSTRUCTIONS = `You are controlling Adobe Premiere Pro through MCP tools. Follow these best practices:
42
43
 
@@ -218,7 +219,7 @@ export function createServer(bridgeOptions) {
218
219
  },
219
220
  {
220
221
  type: "text",
221
- text: "Frame captured successfully.",
222
+ text: t("msg.tool.frameCaptured"),
222
223
  },
223
224
  ],
224
225
  };
@@ -237,7 +238,7 @@ export function createServer(bridgeOptions) {
237
238
  content: [
238
239
  {
239
240
  type: "text",
240
- text: `Error: ${result.error}`,
241
+ text: t("msg.tool.error", { message: String(result.error) }),
241
242
  },
242
243
  ],
243
244
  isError: true,
@@ -249,7 +250,7 @@ export function createServer(bridgeOptions) {
249
250
  content: [
250
251
  {
251
252
  type: "text",
252
- text: `Error: ${err instanceof Error ? err.message : String(err)}`,
253
+ text: t("msg.tool.error", { message: err instanceof Error ? err.message : String(err) }),
253
254
  },
254
255
  ],
255
256
  isError: true,
@@ -259,7 +260,7 @@ export function createServer(bridgeOptions) {
259
260
  }
260
261
  // Register LLM instructions resource
261
262
  server.resource("premiere-instructions", "config://premiere-instructions", {
262
- description: "Instructions and best practices for using Premiere Pro via MCP tools",
263
+ description: t("msg.resource.instructions"),
263
264
  mimeType: "text/plain",
264
265
  }, async (uri) => ({
265
266
  contents: [
@@ -271,7 +272,7 @@ export function createServer(bridgeOptions) {
271
272
  }));
272
273
  // Register ExtendScript API reference resource
273
274
  server.resource("extendscript-reference", "config://extendscript-reference", {
274
- description: "Complete Premiere Pro ExtendScript API reference for writing custom scripts via execute_extendscript",
275
+ description: t("msg.resource.extendscriptReference"),
275
276
  mimeType: "text/plain",
276
277
  }, async (uri) => ({
277
278
  contents: [
@@ -281,6 +282,9 @@ export function createServer(bridgeOptions) {
281
282
  },
282
283
  ],
283
284
  }));
285
+ // Translated tool descriptions in tools/list (both stdio and HTTP entry
286
+ // points build their server here, before connect()). No-op in English.
287
+ localizeToolsList(server);
284
288
  const toolCount = Object.keys(toolModules).length;
285
289
  console.error(`[premiere-pro-mcp] Registered ${toolCount} tools + 2 resources`);
286
290
  return server;
@@ -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 { t } from "../i18n.js";
3
4
  export function getEffectsTools(bridgeOptions) {
4
5
  return {
5
6
  apply_effect: {
@@ -232,7 +233,7 @@ Example: apply_effect({ node_id: "abc123", effect_name: "Lumetri Color" })`,
232
233
  { name: "Clip Name", category: "Utility" },
233
234
  { name: "SDR Conform", category: "Utility" },
234
235
  ];
235
- return { success: true, data: { effects, count: effects.length, note: "Static list of common Premiere Pro video effects. Use apply_effect with these exact names." } };
236
+ return { success: true, data: { effects, count: effects.length, note: t("msg.tool.videoEffectsNote") } };
236
237
  },
237
238
  },
238
239
  list_available_audio_effects: {
@@ -279,7 +280,7 @@ Example: apply_effect({ node_id: "abc123", effect_name: "Lumetri Color" })`,
279
280
  // Time & Pitch
280
281
  { name: "Pitch & Time", category: "Time & Pitch" },
281
282
  ];
282
- return { success: true, data: { effects, count: effects.length, note: "Static list of common Premiere Pro audio effects. Use apply_audio_effect with these exact names." } };
283
+ return { success: true, data: { effects, count: effects.length, note: t("msg.tool.audioEffectsNote") } };
283
284
  },
284
285
  },
285
286
  color_correct: {
@@ -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 { t } from "../i18n.js";
3
4
  import { readFileSync, unlinkSync, existsSync } from "node:fs";
4
5
  import { join } from "node:path";
5
6
  import { tmpdir } from "node:os";
@@ -363,7 +364,7 @@ Example: export_sequence({ output_path: "/Users/me/exports/prores.mov", preset_p
363
364
  attempts++;
364
365
  }
365
366
  if (!existsSync(tempPath)) {
366
- return { success: false, error: "Frame export completed but file not found at: " + tempPath };
367
+ return { success: false, error: t("msg.tool.frameNotFound", { path: tempPath }) };
367
368
  }
368
369
  try {
369
370
  const imageData = readFileSync(tempPath);
@@ -383,7 +384,7 @@ Example: export_sequence({ output_path: "/Users/me/exports/prores.mov", preset_p
383
384
  };
384
385
  }
385
386
  catch (e) {
386
- return { success: false, error: `Failed to read captured frame: ${e instanceof Error ? e.message : String(e)}` };
387
+ return { success: false, error: t("msg.tool.frameReadFailed", { error: e instanceof Error ? e.message : String(e) }) };
387
388
  }
388
389
  },
389
390
  },
@@ -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 { t } from "../i18n.js";
3
4
  export function getTransitionsTools(bridgeOptions) {
4
5
  return {
5
6
  add_transition: {
@@ -247,7 +248,7 @@ export function getTransitionsTools(bridgeOptions) {
247
248
  { name: "Swing Out", category: "3D Motion" },
248
249
  { name: "Tumble Away", category: "3D Motion" },
249
250
  ];
250
- return { success: true, data: { transitions, count: transitions.length, note: "Static list of common Premiere Pro video transitions. Use add_transition with these exact names." } };
251
+ return { success: true, data: { transitions, count: transitions.length, note: t("msg.tool.videoTransitionsNote") } };
251
252
  },
252
253
  },
253
254
  list_available_audio_transitions: {
@@ -259,7 +260,7 @@ export function getTransitionsTools(bridgeOptions) {
259
260
  { name: "Constant Power", category: "Crossfade", description: "Crossfade using constant power (equal loudness) - default" },
260
261
  { name: "Exponential Fade", category: "Crossfade", description: "Exponential fade curve" },
261
262
  ];
262
- return { success: true, data: { transitions, count: transitions.length, note: "Premiere Pro has 3 audio transitions. 'Constant Power' is the recommended default for music crossfades." } };
263
+ return { success: true, data: { transitions, count: transitions.length, note: t("msg.tool.audioTransitionsNote") } };
263
264
  },
264
265
  },
265
266
  };