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
This commit is contained in:
Executable
+383
@@ -0,0 +1,383 @@
|
||||
#!/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);
|
||||
});
|
||||
Reference in New Issue
Block a user