Files
jhonny-editor/bm/premiere-pro-mcp-main/src/index.ts
T
João Henrique b541f502ba feat: initial commit - Jhonny Editor
- Adicionado estrutura completa do projeto
- Configurado MCP server para Premiere Pro
- Adicionado documentação e skills
- Configurado Gitignore para o projeto
2026-09-08 09:59:31 -04:00

384 lines
16 KiB
JavaScript
Executable File

#!/usr/bin/env node
import { StdioServerTransport, serveStdio } from "@modelcontextprotocol/server/stdio";
import { createServer } from "./server.js";
import { cleanupTempDir, getTempDir } from "./bridge/file-bridge.js";
import { getTelemetry } from "./telemetry.js";
import { execFileSync, spawnSync } from "child_process";
import { existsSync } from "fs";
import { fileURLToPath } from "url";
import path from "path";
import { UxpWebSocketBridge } from "./bridge/uxp-websocket-bridge.js";
import {
collectLocalDoctor,
createDoctorRepairPlan,
createSupportBundle,
renderDoctorHuman,
} from "./diagnostics.js";
import { applyDoctorRepairPlan } from "./doctor-repairs.js";
import { compareVersions, fetchLatestNpmVersion } from "./update.js";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.resolve(__dirname, "..");
const debugEnabled = /^(1|true|yes|on|debug)$/i.test(
process.env.PREMIERE_MCP_DEBUG ?? "",
);
function debugLog(message: string): void {
if (debugEnabled) {
console.error(`[premiere-pro-mcp] ${message}`);
}
}
type McpProtocolMode = "auto" | "legacy";
function readMcpProtocolMode(value = process.env.PREMIERE_MCP_PROTOCOL_MODE): McpProtocolMode {
if (value === undefined || value === "" || value === "auto") return "auto";
if (value === "legacy") return "legacy";
throw new Error("PREMIERE_MCP_PROTOCOL_MODE must be either auto or legacy.");
}
function isLoopbackPortInUse(error: unknown): boolean {
return Boolean(
error
&& typeof error === "object"
&& (error as NodeJS.ErrnoException).code === "EADDRINUSE",
);
}
function npmInvocation(): { command: string; prefix: string[] } {
const candidates = [
process.env.npm_execpath,
path.join(path.dirname(process.execPath), "node_modules", "npm", "bin", "npm-cli.js"),
path.join(path.dirname(path.dirname(process.execPath)), "lib", "node_modules", "npm", "bin", "npm-cli.js"),
].filter((candidate): candidate is string => Boolean(candidate && existsSync(candidate)));
if (candidates.length > 0) return { command: process.execPath, prefix: [candidates[0]] };
return { command: process.platform === "win32" ? "npm.cmd" : "npm", prefix: [] };
}
function globalNpmRoot(): string | undefined {
const npm = npmInvocation();
const result = spawnSync(npm.command, [...npm.prefix, "root", "--global"], {
encoding: "utf8",
windowsHide: true,
});
if (result.status !== 0 || !result.stdout) return undefined;
return path.resolve(result.stdout.trim());
}
function isGlobalNpmInstallation(globalRoot: string | undefined): boolean {
if (!globalRoot) return false;
const relative = path.relative(globalRoot, projectRoot);
return relative !== "" && !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative);
}
async function checkForPackageUpdate(): Promise<{ currentVersion: string; latestVersion: string; updateAvailable: boolean }> {
const pkg = await import("../package.json", { with: { type: "json" } });
const currentVersion = String(pkg.default.version ?? "unknown");
const latestVersion = await fetchLatestNpmVersion(String(pkg.default.name ?? "premiere-pro-mcp"));
return {
currentVersion,
latestVersion,
updateAvailable: compareVersions(latestVersion, currentVersion) > 0,
};
}
async function runPackageUpdate(apply: boolean): Promise<void> {
let update;
try {
update = await checkForPackageUpdate();
} catch (error) {
const message = error instanceof Error ? error.message : "unknown error";
console.error(`Could not check npm for an update: ${message}`);
process.exit(1);
}
if (!update.updateAvailable) {
console.log(`premiere-pro-mcp ${update.currentVersion} is current (npm latest: ${update.latestVersion}).`);
if (apply) console.log("To repair the Premiere connector, fully quit Premiere and run premiere-pro-mcp --install-cep.");
return;
}
if (!apply) {
console.log(`Update available: ${update.currentVersion} → ${update.latestVersion}. Run premiere-pro-mcp --update after fully quitting Premiere.`);
return;
}
if (!isGlobalNpmInstallation(globalNpmRoot())) {
console.error("This server is not an npm global installation, so it will not be replaced automatically.");
console.error("For a source checkout, fully quit Premiere and run: npm run update:source");
process.exit(1);
}
const npm = npmInvocation();
console.log(`Updating premiere-pro-mcp ${update.currentVersion} → ${update.latestVersion} and refreshing the Premiere connector...`);
try {
execFileSync(npm.command, [...npm.prefix, "install", "--global", "premiere-pro-mcp@latest"], {
stdio: "inherit",
windowsHide: true,
});
execFileSync(process.execPath, [__filename, "--install-cep"], {
stdio: "inherit",
cwd: projectRoot,
windowsHide: true,
});
} catch {
console.error("The package or connector update did not finish. Premiere must be fully closed before refreshing the connector.");
process.exit(1);
}
console.log("Update complete. Restart Premiere and your MCP client, then run verify_premiere_connection before editing.");
}
// Handle CLI flags
const args = process.argv.slice(2);
if (args.includes("--help") || args.includes("-h")) {
console.log(`
premiere-pro-mcp — MCP server for Adobe Premiere Pro (347 default-profile tools)
Usage:
premiere-pro-mcp Start the MCP server (stdio transport)
premiere-pro-mcp --install-cep Install the CEP plugin into Premiere Pro
premiere-pro-mcp --uninstall-cep Remove this CEP plugin from Premiere Pro
premiere-pro-mcp --diagnose-cep Check the CEP install, debug keys, and Premiere signature logs
premiere-pro-mcp --install-after-effects-cep Install the dedicated CEP plugin into After Effects
premiere-pro-mcp --uninstall-after-effects-cep Remove this CEP plugin from After Effects
premiere-pro-mcp --diagnose-after-effects-cep Check the After Effects CEP install and debug keys
premiere-pro-mcp --doctor Check local install/configuration without reading a project
premiere-pro-mcp --doctor --json Print the same local check as machine-readable JSON
premiere-pro-mcp --doctor --plan-fixes Show a no-write, privacy-safe local repair plan
premiere-pro-mcp --doctor --apply-fixes Apply only safe planned local fixes; requires explicit Premiere-closed confirmation where needed
premiere-pro-mcp --doctor --apply-fixes --confirm-premiere-closed Confirm Premiere is fully closed before a connector repair
premiere-pro-mcp --support-bundle Print a privacy-safe, machine-readable support bundle
premiere-pro-mcp --check-update Check npm for a newer released local server
premiere-pro-mcp --update Update an npm global install and refresh the CEP connector
premiere-pro-mcp --help Show this help message
premiere-pro-mcp --version Show version
Environment variables:
PREMIERE_TEMP_DIR Shared temp directory (default: OS temp + /premiere-mcp-bridge)
PREMIERE_TIMEOUT_MS Command timeout in ms (default: 30000)
PREMIERE_MCP_CAPABILITIES Comma-separated authority profile
PREMIERE_MCP_TOOL_PACKS Comma-separated discovery packs: full, essential, inspection, delivery, captions
PREMIERE_MCP_DEBUG Set to 1/true to enable verbose stderr diagnostics
PREMIERE_MCP_PROTOCOL_MODE auto (default) or legacy; use legacy only when a client cannot complete modern MCP negotiation
PREMIERE_UXP_TOKEN Enable the authenticated local UXP bridge (minimum 16 characters)
PREMIERE_UXP_PORT UXP loopback WebSocket port (default: 7777)
AFTER_EFFECTS_MCP_TEMP_DIR Shared AE bridge directory (default: OS temp + /after-effects-mcp-bridge)
More info: https://github.com/leancoderkavy/premiere-pro-mcp
`);
process.exit(0);
}
const updateActions = ["--check-update", "--update"].filter((flag) => args.includes(flag));
if (updateActions.length > 1) {
console.error("Use only one update action at a time: --check-update or --update.");
process.exit(1);
}
if (updateActions.length === 1) {
await runPackageUpdate(updateActions[0] === "--update");
process.exit(0);
}
if (args.includes("--version") || args.includes("-v")) {
const pkg = await import("../package.json", { with: { type: "json" } }).catch(
() => ({ default: { version: "unknown" } }),
);
console.log(pkg.default.version);
process.exit(0);
}
const doctorPlanRequested = args.includes("--plan-fixes");
const doctorApplyRequested = args.includes("--apply-fixes");
const premiereClosedConfirmed = args.includes("--confirm-premiere-closed");
if ((doctorPlanRequested || doctorApplyRequested) && !args.includes("--doctor")) {
console.error("--plan-fixes and --apply-fixes require --doctor.");
process.exit(1);
}
if (doctorPlanRequested && doctorApplyRequested) {
console.error("Use either --plan-fixes or --apply-fixes, not both.");
process.exit(1);
}
if (premiereClosedConfirmed && !doctorApplyRequested) {
console.error("--confirm-premiere-closed is only valid with --doctor --apply-fixes.");
process.exit(1);
}
if (args.includes("--doctor") || args.includes("--support-bundle")) {
if (args.includes("--support-bundle") && (doctorPlanRequested || doctorApplyRequested)) {
console.error("--support-bundle cannot be combined with --plan-fixes or --apply-fixes.");
process.exit(1);
}
const pkg = await import("../package.json", { with: { type: "json" } }).catch(
() => ({ default: { version: "unknown" } }),
);
if (args.includes("--support-bundle")) {
// A support bundle is JSON by default so it can be attached to an issue or
// support request without copying terminal output. It is a status snapshot,
// never a project/log/configuration dump.
console.log(JSON.stringify(createSupportBundle({ version: pkg.default.version }), null, 2));
} else {
const report = collectLocalDoctor();
if (doctorPlanRequested) {
console.log(JSON.stringify(createDoctorRepairPlan(report), null, 2));
} else if (doctorApplyRequested) {
const result = applyDoctorRepairPlan(createDoctorRepairPlan(report), {
projectRoot,
confirmPremiereClosed: premiereClosedConfirmed,
});
console.log(JSON.stringify(result, null, 2));
if (result.actions.some((action) => action.status === "failed")) process.exit(1);
} else {
console.log(args.includes("--json")
? JSON.stringify(report, null, 2)
: renderDoctorHuman(report));
}
}
process.exit(0);
}
const cepActions = [
"--install-cep", "--uninstall-cep", "--diagnose-cep",
"--install-after-effects-cep", "--uninstall-after-effects-cep", "--diagnose-after-effects-cep",
].filter((flag) => args.includes(flag));
if (cepActions.length > 1) {
console.error("Use only one CEP action at a time.");
process.exit(1);
}
if (cepActions.length === 1) {
const action = cepActions[0];
const diagnose = action === "--diagnose-cep" || action === "--diagnose-after-effects-cep";
const afterEffects = action.includes("after-effects");
const uninstall = action === "--uninstall-cep" || action === "--uninstall-after-effects-cep";
const hostLabel = afterEffects ? "After Effects" : "Premiere Pro";
console.log(uninstall
? `Removing ${hostLabel} CEP plugin...\n`
: diagnose ? `Diagnosing ${hostLabel} CEP plugin...\n` : `Installing ${hostLabel} CEP plugin...\n`);
const isWindows = process.platform === "win32";
const isMacOS = process.platform === "darwin";
if (!isWindows && !isMacOS) {
console.error(
`CEP installation is supported only on Windows and macOS (current platform: ${process.platform}).`,
);
process.exit(1);
}
const scriptPath = path.join(
projectRoot,
"scripts",
isWindows ? "install-cep.ps1" : "install-cep.sh",
);
try {
if (isWindows) {
const powershellArgs = ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", scriptPath];
if (diagnose) powershellArgs.push("-Diagnose");
if (uninstall) {
powershellArgs[4] = path.join(projectRoot, "scripts", "uninstall-cep.ps1");
}
if (afterEffects) powershellArgs.push("-ConnectorHost", "AfterEffects");
execFileSync(
"powershell.exe",
powershellArgs,
{ stdio: "inherit", cwd: projectRoot },
);
} else {
const macosScriptPath = uninstall
? path.join(projectRoot, "scripts", "uninstall-cep.sh")
: scriptPath;
const connectorArgs = [macosScriptPath, uninstall ? "--user" : diagnose ? "--diagnose" : "--copy"];
if (afterEffects) connectorArgs.push("--after-effects");
execFileSync("bash", connectorArgs, {
stdio: "inherit",
cwd: projectRoot,
});
}
} catch {
const operation = uninstall ? "uninstallation" : diagnose ? "diagnostics" : "installation";
console.error(`${hostLabel} CEP ${operation} failed. Try running manually:`);
console.error(
isWindows
? ` powershell -ExecutionPolicy Bypass -File "${uninstall ? path.join(projectRoot, "scripts", "uninstall-cep.ps1") : scriptPath}"${diagnose ? " -Diagnose" : ""}${afterEffects ? " -ConnectorHost AfterEffects" : ""}`
: ` bash "${uninstall ? path.join(projectRoot, "scripts", "uninstall-cep.sh") : scriptPath}" ${uninstall ? "--user" : diagnose ? "--diagnose" : "--copy"}${afterEffects ? " --after-effects" : ""}`,
);
process.exit(1);
}
process.exit(0);
}
async function main() {
process.env.PREMIERE_MCP_TRANSPORT = "stdio";
const protocolMode = readMcpProtocolMode();
const telemetry = getTelemetry();
const bridgeOptions = {
tempDir: process.env.PREMIERE_TEMP_DIR,
timeoutMs: process.env.PREMIERE_TIMEOUT_MS
? parseInt(process.env.PREMIERE_TIMEOUT_MS, 10)
: undefined,
};
const tempDir = getTempDir(bridgeOptions);
debugLog("Starting MCP server...");
debugLog(`Temp directory: ${tempDir}`);
// Clean up any stale files from previous sessions
cleanupTempDir(bridgeOptions);
let uxpBridge: UxpWebSocketBridge | undefined;
if (process.env.PREMIERE_UXP_TOKEN) {
const bridge = new UxpWebSocketBridge({
token: process.env.PREMIERE_UXP_TOKEN,
port: process.env.PREMIERE_UXP_PORT
? parseInt(process.env.PREMIERE_UXP_PORT, 10)
: undefined,
});
try {
await bridge.start();
uxpBridge = bridge;
const address = bridge.address();
debugLog(`UXP bridge listening on ws://${address.host}:${address.port}${address.path}`);
} catch (error) {
if (!isLoopbackPortInUse(error)) throw error;
console.error(
"[premiere-pro-mcp] UXP bridge unavailable because its loopback port is already in use; continuing with CEP-only tools.",
);
}
}
const serverHandle = protocolMode === "legacy"
? await (async () => {
const server = createServer(bridgeOptions, { uxpBridge, telemetry });
const transport = new StdioServerTransport();
await server.connect(transport);
debugLog("Server connected in legacy MCP mode");
return {
close: async () => {
await server.close();
await transport.close();
},
};
})()
: serveStdio(
() => createServer(bridgeOptions, { uxpBridge, telemetry }),
{
onerror: (error) => console.error("[premiere-pro-mcp] MCP stdio error:", error),
},
);
if (protocolMode === "auto") debugLog("Server connected and ready");
const shutdown = async () => {
if (uxpBridge) await uxpBridge.stop();
await serverHandle.close();
await telemetry.shutdown();
};
process.once("SIGINT", () => void shutdown().finally(() => process.exit(0)));
process.once("SIGTERM", () => void shutdown().finally(() => process.exit(0)));
}
main().catch((err) => {
console.error("[premiere-pro-mcp] Fatal error:", err);
process.exit(1);
});