@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 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
- const marker = `${path4.sep}node_modules${path4.sep}ws${path4.sep}`;
1350
- const idx = entry.lastIndexOf(marker);
1351
- if (idx >= 0) return entry.slice(0, idx + marker.length - 1);
1352
- return path4.dirname(entry);
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
- const beside = path4.join(executableDir(), "node_modules", "ws");
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
- function assessPanel(runningHash, installedHash) {
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 installedHash = sha256(path6.join(installed, "jsx", "bundle.jsx"));
1550
- const sourceHash = sha256(path6.join(source, "jsx", "bundle.jsx"));
1551
- const upToDate = installedHash !== null && installedHash === sourceHash;
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: upToDate,
1555
- detail: upToDate ? "installed panel matches this server version" : "installed panel differs from the version shipped with this server",
1556
- fix: upToDate ? void 0 : "Run setup_panel to refresh it, then restart After Effects."
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 needsInstall = by("panelInstalled")?.ok === false || by("panelUpToDate")?.ok === false;
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 (by("afterEffectsRunning")?.ok === false) {
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 ws = wsModuleDir();
1669
- if (ws) {
1670
- const dest = path7.join(target, "node_modules", "ws");
1671
- fs6.mkdirSync(path7.dirname(dest), { recursive: true });
1672
- fs6.rmSync(dest, { recursive: true, force: true });
1673
- copyRecursive(ws, dest);
1674
- actions.push("Copied the `ws` module the panel needs at runtime.");
1675
- } else {
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.0" },
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 assessment = assessPanel(health.bundleHash, installedBundleHash(installedPanelDir()));
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.0";
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.0",
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",
@@ -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.0"
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.0" />
5
+ <Extension Id="games.engine-room.ae-mcp.panel" Version="0.2.1" />
6
6
  </ExtensionList>
7
7
  <ExecutionEnvironment>
8
8
  <HostList>
@@ -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");
@@ -1,5 +1,5 @@
1
1
  // Auto-generated bundle. Do not edit directly — edit files in packages/jsx/.
2
- // Generated 2026-08-12T07:06:29.484Z
2
+ // Generated 2026-08-12T07:48:29.949Z
3
3
 
4
4
  // ===== core.jsx =====
5
5
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@engineroom/ae-panel",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "private": true,
5
5
  "description": "Invisible CEP extension hosting an HTTP+WS bridge inside After Effects.",
6
6
  "dependencies": {