@engine-room/after-effects-mcp 0.2.0 → 0.2.1
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/bin/server.js +103 -35
- package/package.json +1 -1
- package/panel/CSXS/manifest.xml +2 -2
- package/panel/client/main.js +23 -7
- package/panel/jsx/bundle.jsx +1 -1
- package/panel/package.json +1 -1
package/bin/server.js
CHANGED
|
@@ -1342,19 +1342,55 @@ function cepExtensionsDir() {
|
|
|
1342
1342
|
function installedPanelDir() {
|
|
1343
1343
|
return path4.join(cepExtensionsDir(), BUNDLE_ID);
|
|
1344
1344
|
}
|
|
1345
|
+
function isWsModuleDir(dir) {
|
|
1346
|
+
try {
|
|
1347
|
+
const pkg = JSON.parse(fs3.readFileSync(path4.join(dir, "package.json"), "utf8"));
|
|
1348
|
+
if (pkg.name !== "ws") return false;
|
|
1349
|
+
return fs3.existsSync(path4.join(dir, "index.js")) && fs3.existsSync(path4.join(dir, "lib", "websocket.js"));
|
|
1350
|
+
} catch {
|
|
1351
|
+
return false;
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1345
1354
|
function wsModuleDir() {
|
|
1355
|
+
const candidates = [];
|
|
1346
1356
|
try {
|
|
1347
1357
|
const require2 = createRequire(import.meta.url);
|
|
1348
1358
|
const entry = require2.resolve("ws");
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1359
|
+
if (path4.isAbsolute(entry)) {
|
|
1360
|
+
const marker = `${path4.sep}node_modules${path4.sep}ws${path4.sep}`;
|
|
1361
|
+
const idx = entry.lastIndexOf(marker);
|
|
1362
|
+
candidates.push(idx >= 0 ? entry.slice(0, idx + marker.length - 1) : path4.dirname(entry));
|
|
1363
|
+
}
|
|
1364
|
+
} catch {
|
|
1365
|
+
}
|
|
1366
|
+
candidates.push(path4.join(executableDir(), "node_modules", "ws"));
|
|
1367
|
+
candidates.push(path4.join(packageRoot(), "node_modules", "ws"));
|
|
1368
|
+
for (const dir of candidates) {
|
|
1369
|
+
if (isWsModuleDir(dir)) return dir;
|
|
1370
|
+
}
|
|
1371
|
+
return null;
|
|
1372
|
+
}
|
|
1373
|
+
function listPanelFiles(dir, prefix = "") {
|
|
1374
|
+
const out = [];
|
|
1375
|
+
for (const entry of fs3.readdirSync(dir, { withFileTypes: true })) {
|
|
1376
|
+
if (entry.name === "node_modules" || entry.name === ".DS_Store") continue;
|
|
1377
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
1378
|
+
if (entry.isDirectory()) out.push(...listPanelFiles(path4.join(dir, entry.name), rel));
|
|
1379
|
+
else out.push(rel);
|
|
1380
|
+
}
|
|
1381
|
+
return out;
|
|
1382
|
+
}
|
|
1383
|
+
function sameContents(a, b) {
|
|
1384
|
+
try {
|
|
1385
|
+
if (fs3.statSync(a).size !== fs3.statSync(b).size) return false;
|
|
1386
|
+
return fs3.readFileSync(a).equals(fs3.readFileSync(b));
|
|
1353
1387
|
} catch {
|
|
1354
|
-
|
|
1355
|
-
return fs3.existsSync(beside) ? beside : null;
|
|
1388
|
+
return false;
|
|
1356
1389
|
}
|
|
1357
1390
|
}
|
|
1391
|
+
function panelInstallDiff(source, installed) {
|
|
1392
|
+
return listPanelFiles(source).filter((rel) => !sameContents(path4.join(source, rel), path4.join(installed, rel))).sort();
|
|
1393
|
+
}
|
|
1358
1394
|
function copyRecursive(src, dst) {
|
|
1359
1395
|
const stat = fs3.lstatSync(src);
|
|
1360
1396
|
if (stat.isDirectory()) {
|
|
@@ -1391,10 +1427,14 @@ function hashFile(file) {
|
|
|
1391
1427
|
}
|
|
1392
1428
|
}
|
|
1393
1429
|
var STALE_PANEL_ADVICE = "Tell the user this in plain language, then do it: the After Effects panel is older than these tools and does not understand everything they can do now. Run setup_panel, then ask them to quit and reopen After Effects. Do not retry the failed call until they confirm it has restarted.";
|
|
1394
|
-
|
|
1430
|
+
var PARTIAL_INSTALL_ADVICE = "The installed After Effects panel is a mix of two versions \u2014 some files were updated and others were not, which happens when it is installed while After Effects is open and holding them. Restarting will not fix this. Tell the user to quit After Effects completely, then run setup_panel, then reopen it.";
|
|
1431
|
+
function assessPanel(runningHash, installedHash, opts = {}) {
|
|
1395
1432
|
const shipped = sourceBundleHash();
|
|
1396
1433
|
if (!shipped) return { state: "unknown", message: "" };
|
|
1397
1434
|
if (runningHash === shipped) return { state: "current", message: "" };
|
|
1435
|
+
if (opts.installComplete === false) {
|
|
1436
|
+
return { state: "partial-install", message: PARTIAL_INSTALL_ADVICE };
|
|
1437
|
+
}
|
|
1398
1438
|
if (typeof runningHash !== "string" || runningHash.length === 0) {
|
|
1399
1439
|
if (installedHash === shipped) {
|
|
1400
1440
|
return {
|
|
@@ -1545,15 +1585,26 @@ async function checkSetup() {
|
|
|
1545
1585
|
detail: isInstalled ? installed : `not present at ${installed}`,
|
|
1546
1586
|
fix: isInstalled ? void 0 : "Run the setup_panel tool to install it."
|
|
1547
1587
|
});
|
|
1588
|
+
let installComplete = true;
|
|
1548
1589
|
if (isInstalled && source) {
|
|
1549
|
-
const
|
|
1550
|
-
|
|
1551
|
-
const
|
|
1590
|
+
const differing = panelInstallDiff(source, installed);
|
|
1591
|
+
installComplete = differing.length === 0;
|
|
1592
|
+
const shown = differing.slice(0, 4).join(", ");
|
|
1552
1593
|
checks.push({
|
|
1553
1594
|
name: "panelUpToDate",
|
|
1554
|
-
ok:
|
|
1555
|
-
detail:
|
|
1556
|
-
fix:
|
|
1595
|
+
ok: installComplete,
|
|
1596
|
+
detail: installComplete ? "all installed panel files match the version shipped with this server" : `${differing.length} file(s) differ from the version shipped with this server: ${shown}${differing.length > 4 ? ", \u2026" : ""}`,
|
|
1597
|
+
fix: installComplete ? void 0 : "Quit After Effects completely, then run setup_panel, then reopen it. Installing while AE is open can leave some files updated and others not, which is what this is \u2014 restarting alone will not fix it."
|
|
1598
|
+
});
|
|
1599
|
+
}
|
|
1600
|
+
if (isInstalled) {
|
|
1601
|
+
const panelWs = path6.join(installed, "node_modules", "ws");
|
|
1602
|
+
const wsOk = isWsModuleDir(panelWs);
|
|
1603
|
+
checks.push({
|
|
1604
|
+
name: "panelDependencies",
|
|
1605
|
+
ok: wsOk,
|
|
1606
|
+
detail: wsOk ? "the panel's `ws` module is present and complete" : `the panel's \`ws\` module is missing or incomplete at ${panelWs}`,
|
|
1607
|
+
fix: wsOk ? void 0 : "Quit After Effects completely, then run setup_panel, then reopen it. Without `ws` the panel cannot finish starting, so it never answers on its port."
|
|
1557
1608
|
});
|
|
1558
1609
|
}
|
|
1559
1610
|
const running = await isAfterEffectsRunning();
|
|
@@ -1572,12 +1623,14 @@ async function checkSetup() {
|
|
|
1572
1623
|
fix: bridge.ok ? void 0 : "If the other checks pass, restart After Effects so the panel reloads."
|
|
1573
1624
|
});
|
|
1574
1625
|
if (bridge.ok && source) {
|
|
1575
|
-
const assessment = assessPanel(bridge.bundleHash, sha256(path6.join(installed, "jsx", "bundle.jsx"))
|
|
1626
|
+
const assessment = assessPanel(bridge.bundleHash, sha256(path6.join(installed, "jsx", "bundle.jsx")), {
|
|
1627
|
+
installComplete
|
|
1628
|
+
});
|
|
1576
1629
|
const ok = assessment.state === "current";
|
|
1577
1630
|
checks.push({
|
|
1578
1631
|
name: "panelRunningCurrent",
|
|
1579
1632
|
ok,
|
|
1580
|
-
detail: ok ? "After Effects is running the panel that ships with these tools" : assessment.state === "restart-needed" ? "After Effects is still running the previous panel \u2014 the update needs a restart to take effect" : assessment.state === "unknown" ? "the running panel is too old to report its version" : "After Effects is running a panel older than these tools",
|
|
1633
|
+
detail: ok ? "After Effects is running the panel that ships with these tools" : assessment.state === "partial-install" ? "the installed panel files are a mix of versions \u2014 a restart cannot resolve this" : assessment.state === "restart-needed" ? "After Effects is still running the previous panel \u2014 the update needs a restart to take effect" : assessment.state === "unknown" ? "the running panel is too old to report its version" : "After Effects is running a panel older than these tools",
|
|
1581
1634
|
fix: ok ? void 0 : assessment.message
|
|
1582
1635
|
});
|
|
1583
1636
|
}
|
|
@@ -1598,24 +1651,29 @@ function buildNextSteps(checks, ready) {
|
|
|
1598
1651
|
const steps = [];
|
|
1599
1652
|
if (by("platform")?.ok === false) return [by("platform").fix];
|
|
1600
1653
|
if (by("panelAssetsPresent")?.ok === false) return [by("panelAssetsPresent").fix];
|
|
1601
|
-
const
|
|
1654
|
+
const brokenInstall = by("panelUpToDate")?.ok === false || by("panelDependencies")?.ok === false;
|
|
1655
|
+
const needsInstall = by("panelInstalled")?.ok === false || brokenInstall;
|
|
1602
1656
|
const needsDebug = by("cepDebugMode")?.ok === false;
|
|
1657
|
+
const aeRunning = by("afterEffectsRunning")?.ok === true;
|
|
1658
|
+
if (brokenInstall && aeRunning) {
|
|
1659
|
+
steps.push("Quit After Effects completely \u2014 installing while it is open is what leaves the panel half-updated.");
|
|
1660
|
+
}
|
|
1603
1661
|
if (needsDebug || needsInstall) {
|
|
1604
1662
|
steps.push("Run the setup_panel tool \u2014 it installs the After Effects panel and enables the Adobe preference that lets AE load it.");
|
|
1605
1663
|
}
|
|
1606
|
-
if (needsDebug) {
|
|
1607
|
-
steps.push(
|
|
1608
|
-
process.platform === "win32" ? "Quit and reopen After Effects so it re-reads the registry." : "Quit and reopen After Effects. If the panel still does not connect, restart the Mac once \u2014 the Adobe preference sometimes only takes effect after a reboot."
|
|
1609
|
-
);
|
|
1610
|
-
}
|
|
1611
1664
|
const identity = by("panelIdentity");
|
|
1612
1665
|
if (identity && identity.ok === false) {
|
|
1613
1666
|
steps.push(identity.fix);
|
|
1614
1667
|
}
|
|
1615
|
-
if (
|
|
1668
|
+
if (!aeRunning) {
|
|
1616
1669
|
steps.push(needsInstall ? "Open After Effects 2026 \u2014 the panel loads with it." : "Open After Effects 2026.");
|
|
1617
1670
|
} else if (needsInstall || by("panelRunningCurrent")?.ok === false) {
|
|
1618
|
-
steps.push("Quit and reopen After Effects so it picks up the panel.");
|
|
1671
|
+
steps.push(brokenInstall ? "Reopen After Effects." : "Quit and reopen After Effects so it picks up the panel.");
|
|
1672
|
+
}
|
|
1673
|
+
if (needsDebug) {
|
|
1674
|
+
steps.push(
|
|
1675
|
+
process.platform === "win32" ? "After Effects re-reads the registry when it launches, so the preference takes effect then." : "If the panel still does not connect after reopening, restart the Mac once \u2014 the Adobe preference sometimes only takes effect after a reboot."
|
|
1676
|
+
);
|
|
1619
1677
|
}
|
|
1620
1678
|
if (steps.length === 0 && by("bridgeReachable")?.ok === false) {
|
|
1621
1679
|
steps.push("Everything is installed but the panel is not answering. Quit and reopen After Effects.");
|
|
@@ -1642,6 +1700,12 @@ async function installPanel(opts = {}) {
|
|
|
1642
1700
|
if (!fs6.existsSync(path7.join(source, "jsx", "bundle.jsx"))) {
|
|
1643
1701
|
throw new Error(`The panel at ${source} has no jsx/bundle.jsx. In a git checkout, run \`npm run build:jsx\` first.`);
|
|
1644
1702
|
}
|
|
1703
|
+
const ws = wsModuleDir();
|
|
1704
|
+
if (!ws) {
|
|
1705
|
+
throw new Error(
|
|
1706
|
+
"Could not find the `ws` module this server ships. The After Effects panel cannot start without it, so nothing has been changed. Reinstall the server, and please report this with your platform and how you installed it."
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1645
1709
|
const target = installedPanelDir();
|
|
1646
1710
|
const existing = fs6.lstatSync(target, { throwIfNoEntry: false });
|
|
1647
1711
|
if (existing?.isSymbolicLink() && !opts.force) {
|
|
@@ -1665,16 +1729,16 @@ async function installPanel(opts = {}) {
|
|
|
1665
1729
|
fs6.mkdirSync(path7.dirname(target), { recursive: true });
|
|
1666
1730
|
copyRecursive(source, target);
|
|
1667
1731
|
actions.push(`Installed the panel to ${target}.`);
|
|
1668
|
-
const
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
notes.push("Could not locate the `ws` module \u2014 the panel's WebSocket events may not work. Reinstall the package if progress notifications fail.");
|
|
1732
|
+
const dest = path7.join(target, "node_modules", "ws");
|
|
1733
|
+
fs6.mkdirSync(path7.dirname(dest), { recursive: true });
|
|
1734
|
+
fs6.rmSync(dest, { recursive: true, force: true });
|
|
1735
|
+
copyRecursive(ws, dest);
|
|
1736
|
+
if (!isWsModuleDir(dest)) {
|
|
1737
|
+
throw new Error(
|
|
1738
|
+
`Copying \`ws\` from ${ws} to ${dest} did not produce a usable copy. The panel cannot start without it. The panel files are installed but the extension is not yet working \u2014 please report this.`
|
|
1739
|
+
);
|
|
1677
1740
|
}
|
|
1741
|
+
actions.push("Copied the `ws` module the panel needs at runtime.");
|
|
1678
1742
|
let rebootRecommended = false;
|
|
1679
1743
|
if (opts.enableDebugMode !== false) {
|
|
1680
1744
|
const existing2 = await isDebugModeOn();
|
|
@@ -1985,7 +2049,7 @@ var GetJobSchema = schemas_exports.GetJob;
|
|
|
1985
2049
|
var CancelJobSchema = schemas_exports.CancelJob;
|
|
1986
2050
|
function createServer() {
|
|
1987
2051
|
const server = new Server(
|
|
1988
|
-
{ name: "after-effects-mcp", version: "0.2.
|
|
2052
|
+
{ name: "after-effects-mcp", version: "0.2.1" },
|
|
1989
2053
|
{
|
|
1990
2054
|
capabilities: { tools: {}, logging: {}, prompts: {}, resources: {} },
|
|
1991
2055
|
// Clients that honour this fold it into the system prompt, which is the
|
|
@@ -2192,7 +2256,11 @@ function createPanelGate(bridge) {
|
|
|
2192
2256
|
if (Date.now() - checkedAt < RECHECK_MS) return null;
|
|
2193
2257
|
try {
|
|
2194
2258
|
const health = await bridge.health();
|
|
2195
|
-
const
|
|
2259
|
+
const installed = installedPanelDir();
|
|
2260
|
+
const source = panelSourceDir();
|
|
2261
|
+
const assessment = assessPanel(health.bundleHash, installedBundleHash(installed), {
|
|
2262
|
+
installComplete: source ? panelInstallDiff(source, installed).length === 0 : void 0
|
|
2263
|
+
});
|
|
2196
2264
|
checkedAt = Date.now();
|
|
2197
2265
|
verdict = assessment.state === "current" || assessment.state === "unknown" ? null : assessment.message;
|
|
2198
2266
|
if (assessment.state === "unknown" && assessment.message) logger.warn(assessment.message);
|
|
@@ -2291,7 +2359,7 @@ ${USAGE}`);
|
|
|
2291
2359
|
await server.connect(transport);
|
|
2292
2360
|
logger.info("MCP server running on stdio");
|
|
2293
2361
|
}
|
|
2294
|
-
var VERSION = "0.2.
|
|
2362
|
+
var VERSION = "0.2.1";
|
|
2295
2363
|
main().catch((e) => {
|
|
2296
2364
|
logger.error("fatal", e.message);
|
|
2297
2365
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@engine-room/after-effects-mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Control Adobe After Effects with AI — describe the animation you want and it gets built: layers, keyframes, effects, expressions and text, all editable afterwards.",
|
|
6
6
|
"license": "MIT",
|
package/panel/CSXS/manifest.xml
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
-
<ExtensionManifest Version="11.0" ExtensionBundleId="games.engine-room.ae-mcp" ExtensionBundleVersion="0.2.
|
|
2
|
+
<ExtensionManifest Version="11.0" ExtensionBundleId="games.engine-room.ae-mcp" ExtensionBundleVersion="0.2.1"
|
|
3
3
|
ExtensionBundleName="AE MCP Bridge" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
4
4
|
<ExtensionList>
|
|
5
|
-
<Extension Id="games.engine-room.ae-mcp.panel" Version="0.2.
|
|
5
|
+
<Extension Id="games.engine-room.ae-mcp.panel" Version="0.2.1" />
|
|
6
6
|
</ExtensionList>
|
|
7
7
|
<ExecutionEnvironment>
|
|
8
8
|
<HostList>
|
package/panel/client/main.js
CHANGED
|
@@ -11,14 +11,12 @@
|
|
|
11
11
|
var os = require("os");
|
|
12
12
|
var http = require("http");
|
|
13
13
|
var crypto = require("crypto");
|
|
14
|
-
var WebSocket;
|
|
15
|
-
try { WebSocket = require("ws"); }
|
|
16
|
-
catch (e) {
|
|
17
|
-
// ws is bundled in packages/ae-panel/node_modules; resolve manually if normal require fails.
|
|
18
|
-
var alt = path.join(__dirname, "..", "node_modules", "ws");
|
|
19
|
-
WebSocket = require(alt);
|
|
20
|
-
}
|
|
21
14
|
|
|
15
|
+
// The DOM handles and the logger come first so that everything below is able
|
|
16
|
+
// to report its own failure. `require("ws")` used to run before this point,
|
|
17
|
+
// so when it threw the exception escaped this whole function before a single
|
|
18
|
+
// line could be written — the panel sat on "starting…" indefinitely with the
|
|
19
|
+
// reason nowhere to be seen.
|
|
22
20
|
var $status = document.getElementById("status");
|
|
23
21
|
var $port = document.getElementById("port");
|
|
24
22
|
var $ae = document.getElementById("ae");
|
|
@@ -41,6 +39,24 @@
|
|
|
41
39
|
while ($log.childNodes.length > 80) $log.removeChild($log.lastChild);
|
|
42
40
|
}
|
|
43
41
|
|
|
42
|
+
var WebSocket;
|
|
43
|
+
try { WebSocket = require("ws"); }
|
|
44
|
+
catch (primary) {
|
|
45
|
+
// ws is bundled in packages/ae-panel/node_modules; resolve manually if normal require fails.
|
|
46
|
+
var alt = path.join(__dirname, "..", "node_modules", "ws");
|
|
47
|
+
try { WebSocket = require(alt); }
|
|
48
|
+
catch (fallback) {
|
|
49
|
+
// Nothing below can be built without ws, so this stops here — but it
|
|
50
|
+
// stops saying why, and naming the fix, rather than looking like a panel
|
|
51
|
+
// that is still starting up.
|
|
52
|
+
setStatus("cannot start — the ws module is missing", "err");
|
|
53
|
+
log("error", "require('ws') failed: " + primary.message);
|
|
54
|
+
log("error", "and " + alt + ": " + fallback.message);
|
|
55
|
+
log("error", "Quit After Effects, run the setup_panel tool, then reopen it.");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
44
60
|
var cs = new CSInterface();
|
|
45
61
|
var extDir = cs.getSystemPath(SystemPath.EXTENSION);
|
|
46
62
|
var bundlePath = path.join(extDir, "jsx", "bundle.jsx");
|
package/panel/jsx/bundle.jsx
CHANGED