@engine-room/after-effects-mcp 0.1.1 → 0.1.2

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/README.md CHANGED
@@ -94,6 +94,7 @@ It reinstalls the panel and tells you to restart After Effects. Ask it to check
94
94
  | Raw (1) | `run_jsx` |
95
95
  | Jobs (3) | `await_job`, `get_job`, `cancel_job` |
96
96
  | Setup (2) | `check_setup`, `setup_panel` |
97
+ | Issues (3) | `list_known_issues`, `log_issue`, `mark_issue_reported` |
97
98
 
98
99
  A few notes on the ones that behave differently from the rest:
99
100
 
@@ -101,6 +102,30 @@ A few notes on the ones that behave differently from the rest:
101
102
  - `run_batch` runs many operations in a single pass and counts as one undo step. Long batches stream progress.
102
103
  - `screenshot_frame` and `screenshot_layer` are for occasional checks, not for reviewing motion frame by frame. On large comps, `downsample: 2` renders at half resolution, which is faster and keeps the image small.
103
104
  - `run_jsx` runs arbitrary ExtendScript for anything the other tools do not cover.
105
+ - `log_issue` and `list_known_issues` are the notebook described below.
106
+
107
+ ## When something goes wrong
108
+
109
+ These tools have rough edges. When the AI hits one and works out a way around
110
+ it, it writes the problem and the fix into a notebook in your project folder, at
111
+ `.ae-mcp/issues/` — plain text files you can read or delete. The next session
112
+ reads that notebook before guessing, so the same twenty minutes are never spent
113
+ twice on the same project.
114
+
115
+ The folder ignores itself, so it stays out of version control without you doing
116
+ anything.
117
+
118
+ If the problem looks like ours rather than yours, the AI will say so at the end
119
+ of its reply and offer to pass it on. You can also start that yourself:
120
+
121
+ ```
122
+ /report-ae-issue
123
+ ```
124
+
125
+ It writes the report, shows it to you, and only sends it once you say yes.
126
+ Nothing about your own work — comp names, file paths, clients — goes into it.
127
+ Sending needs the [GitHub CLI](https://cli.github.com); without it you get a
128
+ prefilled link to click instead.
104
129
 
105
130
  ## Troubleshooting
106
131
 
package/bin/server.js CHANGED
@@ -65,6 +65,39 @@ description: The visual and motion style for this project \u2014 palette, type,
65
65
  - _Anything that should always or never happen. E.g. "never put text directly on
66
66
  footage \u2014 always on a rounded chip", "keep total runtime under 8 seconds"._
67
67
  `;
68
+ var REPORT_COMMAND = `---
69
+ description: Send a problem you hit with the After Effects tools to the people who maintain them
70
+ argument-hint: "[what went wrong, in your own words]"
71
+ ---
72
+
73
+ # Report a problem with the After Effects tools
74
+
75
+ Assume the person you are helping is a motion designer, not a developer, and may
76
+ never have used GitHub. Do the technical part for them.
77
+
78
+ 1. **Find what to report.** Call \`list_known_issues\` with \`status: "unreported"\`.
79
+ It returns what earlier sessions wrote down, plus \`repo\`, \`newIssueUrl\`,
80
+ \`serverVersion\` and \`platform\`. List the entries in plain sentences \u2014 not raw
81
+ titles \u2014 and ask which to send. If there is nothing recorded but \`$ARGUMENTS\`
82
+ describes a problem, ask what they were doing and what happened, then
83
+ \`log_issue\` it first. If there is nothing at all, say so and stop.
84
+
85
+ 2. **Draft it short.** Title: one concrete line. Body: **What happens** (the
86
+ failing call and exact error), **Why** if known, **Workaround**, and
87
+ **Environment** (\`after-effects-mcp <serverVersion> \xB7 <platform> \xB7 After
88
+ Effects 2026\`). Leave out their own work \u2014 comp names, file paths, client
89
+ names, anything about the video.
90
+
91
+ 3. **Show it and ask.** This posts publicly, so get a real yes.
92
+
93
+ 4. **Send it.** \`gh issue create --repo <repo> --title "..." --body "..."\`. If
94
+ \`gh\` is missing or not logged in, do not install it \u2014 build a prefilled link
95
+ instead by URL-encoding the title and body onto \`<newIssueUrl>\` as
96
+ \`?title=\u2026&body=\u2026\`, and tell them to open it and press the green button.
97
+
98
+ 5. **Close the loop.** On success call \`mark_issue_reported\` with the entry id
99
+ and URL, then give them the link. If they decline, leave the entry unreported.
100
+ `;
68
101
  var CLAUDE_MD = `# {{NAME}}
69
102
 
70
103
  After Effects project folder. The tools drive After Effects directly from here.
@@ -82,6 +115,14 @@ The look of everything built here is defined in
82
115
  \`.claude/skills/house-style/SKILL.md\`. Edit that file to change the defaults \u2014
83
116
  palette, type, timing, layout. It is read automatically.
84
117
 
118
+ ## When a tool misbehaves
119
+
120
+ Check \`list_known_issues\` before guessing \u2014 an earlier session may already have
121
+ solved it. When you work out a fix for something that cost real time and was the
122
+ tool's fault rather than yours, record it with \`log_issue\` so the next session
123
+ does not pay for it again, and offer at the end of your reply to pass it on.
124
+ \`/report-ae-issue\` sends it to the maintainers.
125
+
85
126
  ## Conventions for this project
86
127
 
87
128
  <!-- Anything specific to this project rather than to your general style:
@@ -123,6 +164,7 @@ Usage: npx @engine-room/after-effects-mcp init <directory> [--no-mcp]
123
164
  const files = [
124
165
  ["CLAUDE.md", CLAUDE_MD.replace("{{NAME}}", name)],
125
166
  [path.join(".claude", "skills", "house-style", "SKILL.md"), HOUSE_STYLE],
167
+ [path.join(".claude", "commands", "report-ae-issue.md"), REPORT_COMMAND],
126
168
  [path.join("renders", ".gitkeep"), ""]
127
169
  ];
128
170
  if (parsed.withMcp) files.push([".mcp.json", MCP_JSON]);
@@ -147,6 +189,7 @@ Delete them first, or choose a different directory.
147
189
  ``,
148
190
  ` CLAUDE.md what this project is`,
149
191
  ` .claude/skills/house-style/SKILL.md your look \u2014 edit this first`,
192
+ ` .claude/commands/report-ae-issue.md /report-ae-issue \u2014 tell the maintainers something broke`,
150
193
  ` renders/ exports land here`,
151
194
  ...parsed.withMcp ? [` .mcp.json connects your client to After Effects`] : [],
152
195
  ``,
@@ -262,7 +305,10 @@ __export(schemas_exports, {
262
305
  ListAvailableEffects: () => ListAvailableEffects,
263
306
  ListComps: () => ListComps,
264
307
  ListEffects: () => ListEffects,
308
+ ListKnownIssues: () => ListKnownIssues,
265
309
  ListLayers: () => ListLayers,
310
+ LogIssue: () => LogIssue,
311
+ MarkIssueReported: () => MarkIssueReported,
266
312
  OpSchemas: () => OpSchemas,
267
313
  ParentLayer: () => ParentLayer,
268
314
  PropertyPath: () => PropertyPath,
@@ -669,6 +715,21 @@ var SetupPanel = z2.object({
669
715
  enableDebugMode: z2.boolean().default(true).optional().describe("Also enable Adobe's PlayerDebugMode preference, which AE requires to load this unsigned panel. Default true."),
670
716
  force: z2.boolean().default(false).optional().describe("Replace an existing symlinked (development) install with a copy. Default false.")
671
717
  }).strict();
718
+ var LogIssue = z2.object({
719
+ title: z2.string().min(3).describe("One line naming the problem, specific enough to recognise again. Becomes the entry's id."),
720
+ symptom: z2.string().min(3).describe("What went wrong, including the exact error text and the call that produced it."),
721
+ workaround: z2.string().min(3).describe("What actually worked \u2014 concrete enough for the next session to apply without rediscovering it."),
722
+ cause: z2.string().optional().describe("Why it happens, if you worked it out."),
723
+ tools: z2.array(z2.string()).optional().describe("Tool names involved, e.g. ['set_temporal_ease'].")
724
+ }).strict();
725
+ var ListKnownIssues = z2.object({
726
+ status: z2.enum(["all", "unreported", "reported"]).default("all").optional(),
727
+ tool: z2.string().optional().describe("Only entries about this tool, e.g. 'set_temporal_ease'. Omit for everything.")
728
+ }).strict();
729
+ var MarkIssueReported = z2.object({
730
+ id: z2.string().describe("The entry id returned by log_issue or list_known_issues."),
731
+ url: z2.string().optional().describe("Link to the issue that was opened.")
732
+ }).strict();
672
733
  var AwaitJob = z2.object({ jobId: z2.string(), timeoutMs: z2.number().int().positive().default(6e5).optional() });
673
734
  var GetJob = z2.object({ jobId: z2.string() });
674
735
  var CancelJob = z2.object({ jobId: z2.string() });
@@ -748,7 +809,11 @@ var OpSchemas = {
748
809
  cancel_job: CancelJob,
749
810
  // setup
750
811
  check_setup: CheckSetup,
751
- setup_panel: SetupPanel
812
+ setup_panel: SetupPanel,
813
+ // issue journal
814
+ log_issue: LogIssue,
815
+ list_known_issues: ListKnownIssues,
816
+ mark_issue_reported: MarkIssueReported
752
817
  };
753
818
 
754
819
  // src/util/errors.ts
@@ -1106,7 +1171,11 @@ var descriptions = {
1106
1171
  cancel_job: "Set cancel flag; chunked loop stops at next boundary.",
1107
1172
  // ---------- setup ----------
1108
1173
  check_setup: "Diagnose the After Effects connection: panel installed, up to date, Adobe debug preference on, AE running, bridge answering. Read-only and safe to call any time. Call this FIRST whenever another tool reports it cannot reach After Effects, then relay `nextSteps` to the user in plain language.",
1109
- setup_panel: "Install or refresh the After Effects panel and enable the Adobe preference AE needs to load it. Run this when check_setup reports the panel is missing or out of date. It writes to the user's Adobe CEP extensions folder and sets a user-level Adobe preference \u2014 tell the user what it will do before calling it. Afterwards, AE must be restarted; if the preference was newly enabled, a one-time Mac reboot may also be needed."
1174
+ setup_panel: "Install or refresh the After Effects panel and enable the Adobe preference AE needs to load it. Run this when check_setup reports the panel is missing or out of date. It writes to the user's Adobe CEP extensions folder and sets a user-level Adobe preference \u2014 tell the user what it will do before calling it. Afterwards, AE must be restarted; if the preference was newly enabled, a one-time Mac reboot may also be needed.",
1175
+ // ---------- issue journal ----------
1176
+ list_known_issues: "Problems earlier sessions hit with these tools, with the workarounds that worked. Read it when a tool fails in a way you don't immediately understand \u2014 pass `tool` to narrow it to that one \u2014 and before nontrivial work. It can save you rediscovering a fix that already cost someone an hour. Also returns the repo and server version needed to report one.",
1177
+ log_issue: "Record a problem you hit and the workaround that got past it, so the next session doesn't rediscover it. Log only what cost real effort and will recur: a tool failing for a non-obvious reason, an argument shape the schema didn't imply, AE behaving unlike the docs. Not your own typos, not one-off user mistakes. Call list_known_issues first and reuse the same title to extend an existing entry rather than duplicating it. If the result comes back with reported:false, then AFTER you have finished the actual work, close your reply by telling the user in plain language that something took much longer than it should have and offering to pass it to the people who maintain this tool \u2014 phrase it for a motion designer, in terms of what actually happened, and don't say 'GitHub issue' or 'bug report' unless they say it first.",
1178
+ mark_issue_reported: "Record that a journal entry has been sent to the maintainers, with the resulting URL. Call it only once the issue really exists, so later sessions don't ask the user to report the same thing twice."
1110
1179
  };
1111
1180
 
1112
1181
  // src/setup/check.ts
@@ -1134,6 +1203,14 @@ function packageRoot() {
1134
1203
  }
1135
1204
  return path3.dirname(fileURLToPath(import.meta.url));
1136
1205
  }
1206
+ function packageVersion() {
1207
+ try {
1208
+ const pkg = JSON.parse(fs3.readFileSync(path3.join(packageRoot(), "package.json"), "utf8"));
1209
+ return typeof pkg.version === "string" ? pkg.version : "unknown";
1210
+ } catch {
1211
+ return "unknown";
1212
+ }
1213
+ }
1137
1214
  function panelSourceDir() {
1138
1215
  const candidates = [
1139
1216
  // The live workspace copy comes first so a git checkout always installs
@@ -1456,6 +1533,208 @@ async function installPanel(opts = {}) {
1456
1533
  };
1457
1534
  }
1458
1535
 
1536
+ // src/issues/journal.ts
1537
+ import fs6 from "node:fs";
1538
+ import os3 from "node:os";
1539
+ import path6 from "node:path";
1540
+ var REPO = "Engine-Room-Games/after-effects-mcp";
1541
+ var NEW_ISSUE_URL = `https://github.com/${REPO}/issues/new`;
1542
+ var SECTION_SYMPTOM = "What went wrong";
1543
+ var SECTION_CAUSE = "Why";
1544
+ var SECTION_WORKAROUND = "What worked";
1545
+ function journalRoot() {
1546
+ const override = process.env.AE_MCP_HOME?.trim();
1547
+ if (override && override.length > 0) return { dir: override, scope: "project" };
1548
+ const cwd = process.cwd();
1549
+ const unusable = cwd === path6.parse(cwd).root || cwd === os3.homedir();
1550
+ if (!unusable) {
1551
+ try {
1552
+ fs6.accessSync(cwd, fs6.constants.W_OK);
1553
+ return { dir: path6.join(cwd, ".ae-mcp"), scope: "project" };
1554
+ } catch {
1555
+ }
1556
+ }
1557
+ return { dir: path6.join(os3.homedir(), ".after-effects-mcp"), scope: "home" };
1558
+ }
1559
+ function journalDir() {
1560
+ return path6.join(journalRoot().dir, "issues");
1561
+ }
1562
+ function ensureJournalDir() {
1563
+ const { dir } = journalRoot();
1564
+ const issues = path6.join(dir, "issues");
1565
+ fs6.mkdirSync(issues, { recursive: true });
1566
+ const ignore = path6.join(dir, ".gitignore");
1567
+ if (!fs6.existsSync(ignore)) fs6.writeFileSync(ignore, "*\n", "utf8");
1568
+ return issues;
1569
+ }
1570
+ function slugify(text) {
1571
+ const slug = text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/g, "");
1572
+ return slug.length > 0 ? slug : `issue-${Date.now()}`;
1573
+ }
1574
+ function today() {
1575
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1576
+ }
1577
+ function oneLine(text) {
1578
+ return text.replace(/\s+/g, " ").trim();
1579
+ }
1580
+ function entryPath(id) {
1581
+ const dir = path6.resolve(journalDir());
1582
+ const file = path6.resolve(dir, `${id}.md`);
1583
+ if (path6.dirname(file) !== dir) throw new Error(`Invalid issue id: ${id}`);
1584
+ return file;
1585
+ }
1586
+ function render(entry) {
1587
+ const lines = [
1588
+ "---",
1589
+ `id: ${entry.id}`,
1590
+ `title: ${oneLine(entry.title)}`,
1591
+ `tools: ${entry.tools.join(", ")}`,
1592
+ `firstSeen: ${entry.firstSeen}`,
1593
+ `lastSeen: ${entry.lastSeen}`,
1594
+ `occurrences: ${entry.occurrences}`,
1595
+ `reported: ${entry.reported}`,
1596
+ `issueUrl: ${entry.issueUrl ?? ""}`,
1597
+ "---",
1598
+ "",
1599
+ `## ${SECTION_SYMPTOM}`,
1600
+ "",
1601
+ entry.symptom.trim(),
1602
+ ""
1603
+ ];
1604
+ if (entry.cause && entry.cause.trim().length > 0) {
1605
+ lines.push(`## ${SECTION_CAUSE}`, "", entry.cause.trim(), "");
1606
+ }
1607
+ lines.push(`## ${SECTION_WORKAROUND}`, "", entry.workaround.trim(), "");
1608
+ return lines.join("\n");
1609
+ }
1610
+ function readSections(body) {
1611
+ const marks = [];
1612
+ for (const heading of [SECTION_SYMPTOM, SECTION_CAUSE, SECTION_WORKAROUND]) {
1613
+ const m = new RegExp(`^##[ \\t]+${heading}[ \\t]*$`, "im").exec(body);
1614
+ if (m) marks.push({ key: heading.toLowerCase(), from: m.index, to: m.index + m[0].length });
1615
+ }
1616
+ marks.sort((a, b) => a.from - b.from);
1617
+ const sections = /* @__PURE__ */ new Map();
1618
+ marks.forEach((mark, i) => {
1619
+ const end = i + 1 < marks.length ? marks[i + 1].from : body.length;
1620
+ sections.set(mark.key, body.slice(mark.to, end).trim());
1621
+ });
1622
+ return sections;
1623
+ }
1624
+ function parse(text, fallbackId) {
1625
+ const meta = {};
1626
+ let body = text;
1627
+ const fm = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text);
1628
+ if (fm) {
1629
+ for (const line of fm[1].split(/\r?\n/)) {
1630
+ const sep = line.indexOf(":");
1631
+ if (sep <= 0) continue;
1632
+ meta[line.slice(0, sep).trim()] = line.slice(sep + 1).trim();
1633
+ }
1634
+ body = text.slice(fm[0].length);
1635
+ }
1636
+ const sections = readSections(body);
1637
+ const occurrences = Number.parseInt(meta.occurrences ?? "1", 10);
1638
+ return {
1639
+ id: meta.id || fallbackId,
1640
+ title: meta.title || fallbackId.replace(/-/g, " "),
1641
+ tools: (meta.tools ?? "").split(",").map((t) => t.trim()).filter((t) => t.length > 0),
1642
+ firstSeen: meta.firstSeen || "",
1643
+ lastSeen: meta.lastSeen || meta.firstSeen || "",
1644
+ occurrences: Number.isFinite(occurrences) && occurrences > 0 ? occurrences : 1,
1645
+ reported: meta.reported === "true",
1646
+ issueUrl: meta.issueUrl && meta.issueUrl.length > 0 ? meta.issueUrl : void 0,
1647
+ // A hand-edited file with no recognised headings still has its text kept,
1648
+ // rather than being silently reduced to an empty entry.
1649
+ symptom: sections.get(SECTION_SYMPTOM.toLowerCase()) ?? (sections.size === 0 ? body.trim() : ""),
1650
+ cause: sections.get(SECTION_CAUSE.toLowerCase()) || void 0,
1651
+ workaround: sections.get(SECTION_WORKAROUND.toLowerCase()) ?? ""
1652
+ };
1653
+ }
1654
+ function readEntry(file) {
1655
+ try {
1656
+ return parse(fs6.readFileSync(file, "utf8"), path6.basename(file, ".md"));
1657
+ } catch {
1658
+ return null;
1659
+ }
1660
+ }
1661
+ function logIssue(input) {
1662
+ const id = slugify(input.title);
1663
+ const file = entryPath(id);
1664
+ const existing = fs6.existsSync(file) ? readEntry(file) : null;
1665
+ const entry = {
1666
+ id,
1667
+ title: oneLine(input.title),
1668
+ tools: input.tools ?? existing?.tools ?? [],
1669
+ firstSeen: existing?.firstSeen || today(),
1670
+ lastSeen: today(),
1671
+ // Repeats are worth counting: an entry seen five times is the one most
1672
+ // worth reporting, and the count is the only evidence of that.
1673
+ occurrences: (existing?.occurrences ?? 0) + 1,
1674
+ // Reporting state belongs to the entry, not to this sighting — a fresh
1675
+ // description of a known problem must not un-report it.
1676
+ reported: existing?.reported ?? false,
1677
+ issueUrl: existing?.issueUrl,
1678
+ symptom: input.symptom,
1679
+ // A cause worked out once is not lost because a later sighting was logged
1680
+ // without one.
1681
+ cause: input.cause ?? existing?.cause,
1682
+ workaround: input.workaround
1683
+ };
1684
+ ensureJournalDir();
1685
+ fs6.writeFileSync(file, render(entry), "utf8");
1686
+ return {
1687
+ id,
1688
+ path: file,
1689
+ occurrences: entry.occurrences,
1690
+ previouslyLogged: existing !== null,
1691
+ reported: entry.reported,
1692
+ issueUrl: entry.issueUrl
1693
+ };
1694
+ }
1695
+ function listIssues(status = "all", tool) {
1696
+ const { scope } = journalRoot();
1697
+ const dir = journalDir();
1698
+ let entries = [];
1699
+ try {
1700
+ entries = fs6.readdirSync(dir).filter((f) => f.endsWith(".md")).map((f) => readEntry(path6.join(dir, f))).filter((e) => e !== null);
1701
+ } catch {
1702
+ entries = [];
1703
+ }
1704
+ const wanted = tool?.trim().toLowerCase();
1705
+ const filtered = entries.filter((e) => {
1706
+ const byStatus = status === "all" ? true : status === "reported" ? e.reported : !e.reported;
1707
+ if (!byStatus) return false;
1708
+ if (!wanted) return true;
1709
+ return e.tools.some((t) => t.toLowerCase() === wanted) || e.title.toLowerCase().includes(wanted);
1710
+ });
1711
+ filtered.sort((a, b) => b.lastSeen.localeCompare(a.lastSeen) || b.occurrences - a.occurrences);
1712
+ return {
1713
+ dir,
1714
+ scope,
1715
+ repo: REPO,
1716
+ newIssueUrl: NEW_ISSUE_URL,
1717
+ serverVersion: packageVersion(),
1718
+ platform: process.platform,
1719
+ count: filtered.length,
1720
+ issues: filtered
1721
+ };
1722
+ }
1723
+ function markReported(id, url) {
1724
+ const file = entryPath(slugify(id));
1725
+ const entry = fs6.existsSync(file) ? readEntry(file) : null;
1726
+ if (!entry) {
1727
+ const known = listIssues("all").issues.map((e) => e.id);
1728
+ throw new Error(
1729
+ `No journal entry with id "${id}".` + (known.length > 0 ? ` Known ids: ${known.join(", ")}` : "")
1730
+ );
1731
+ }
1732
+ entry.reported = true;
1733
+ if (url) entry.issueUrl = oneLine(url);
1734
+ fs6.writeFileSync(file, render(entry), "utf8");
1735
+ return entry;
1736
+ }
1737
+
1459
1738
  // src/util/pngImage.ts
1460
1739
  function imageContent(meta, base64) {
1461
1740
  return {
@@ -1470,13 +1749,22 @@ function imageContent(meta, base64) {
1470
1749
  var { OpSchemas: OpSchemas2 } = schemas_exports;
1471
1750
  var VISION_OPS = /* @__PURE__ */ new Set(["screenshot_frame", "screenshot_layer"]);
1472
1751
  var ASYNC_OPS = /* @__PURE__ */ new Set(["run_batch"]);
1473
- var SERVER_OPS = /* @__PURE__ */ new Set(["await_job", "get_job", "cancel_job", "check_setup", "setup_panel"]);
1752
+ var SERVER_OPS = /* @__PURE__ */ new Set([
1753
+ "await_job",
1754
+ "get_job",
1755
+ "cancel_job",
1756
+ "check_setup",
1757
+ "setup_panel",
1758
+ "log_issue",
1759
+ "list_known_issues",
1760
+ "mark_issue_reported"
1761
+ ]);
1474
1762
  var AwaitJobSchema = schemas_exports.AwaitJob;
1475
1763
  var GetJobSchema = schemas_exports.GetJob;
1476
1764
  var CancelJobSchema = schemas_exports.CancelJob;
1477
1765
  function createServer() {
1478
1766
  const server = new Server(
1479
- { name: "after-effects-mcp", version: "0.1.1" },
1767
+ { name: "after-effects-mcp", version: "0.1.2" },
1480
1768
  { capabilities: { tools: {}, logging: {} } }
1481
1769
  );
1482
1770
  const bridge = new HttpClient();
@@ -1535,6 +1823,19 @@ function createServer() {
1535
1823
  const installed = await installPanel({ enableDebugMode: a.enableDebugMode, force: a.force });
1536
1824
  return textResult({ ...installed, setup: await checkSetup() });
1537
1825
  }
1826
+ if (name === "log_issue") {
1827
+ const a = schemas_exports.LogIssue.parse(rawArgs);
1828
+ return textResult(logIssue(a));
1829
+ }
1830
+ if (name === "list_known_issues") {
1831
+ const a = schemas_exports.ListKnownIssues.parse(rawArgs);
1832
+ return textResult(listIssues(a.status ?? "all", a.tool));
1833
+ }
1834
+ if (name === "mark_issue_reported") {
1835
+ const a = schemas_exports.MarkIssueReported.parse(rawArgs);
1836
+ const entry = markReported(a.id, a.url);
1837
+ return textResult({ ok: true, id: entry.id, reported: true, issueUrl: entry.issueUrl });
1838
+ }
1538
1839
  } catch (e) {
1539
1840
  return errorResult(e.message);
1540
1841
  }
@@ -1663,7 +1964,7 @@ ${USAGE}`);
1663
1964
  await server.connect(transport);
1664
1965
  logger.info("MCP server running on stdio");
1665
1966
  }
1666
- var VERSION = "0.1.1";
1967
+ var VERSION = "0.1.2";
1667
1968
  main().catch((e) => {
1668
1969
  logger.error("fatal", e.message);
1669
1970
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@engine-room/after-effects-mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
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.1.1"
2
+ <ExtensionManifest Version="11.0" ExtensionBundleId="games.engine-room.ae-mcp" ExtensionBundleVersion="0.1.2"
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.1.1" />
5
+ <Extension Id="games.engine-room.ae-mcp.panel" Version="0.1.2" />
6
6
  </ExtensionList>
7
7
  <ExecutionEnvironment>
8
8
  <HostList>
@@ -1,5 +1,5 @@
1
1
  // Auto-generated bundle. Do not edit directly — edit files in packages/jsx/.
2
- // Generated 2026-08-10T15:26:38.133Z
2
+ // Generated 2026-08-11T09:11:46.741Z
3
3
 
4
4
  // ===== core.jsx =====
5
5
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@engineroom/ae-panel",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": true,
5
5
  "description": "Invisible CEP extension hosting an HTTP+WS bridge inside After Effects.",
6
6
  "dependencies": {