- Adicionado estrutura completa do projeto - Configurado MCP server para Premiere Pro - Adicionado documentação e skills - Configurado Gitignore para o projeto
384 lines
16 KiB
JavaScript
Executable File
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);
|
|
});
|