Skip to main content
Glama

AE Bridge MCP

A Model Context Protocol server that lets an AI agent (Claude, or any MCP-compatible client) inspect and drive a live Adobe After Effects session — read the active composition, list selected layers, inspect a layer's transform/effects, or evaluate arbitrary ExtendScript.

Extracted from Dimension's ae_bridge_mcp/ae_eval dev tooling so other NeuralIO 444 AE-adjacent products can reuse it without depending on Dimension itself.

Part of a small set of related repos:

  • AE_Eval — the same evaluate-ExtendScript-in-AE capability as a plain terminal CLI, no MCP client needed. Independent of this repo — pick whichever fits, or use both (they share the same AE-side listener file).

  • IPC_Client — the general-purpose version of the socket transport both of the above are built on.

What it does

Four moving pieces:

  1. ae_bridge_mcp/jsx/ae_bridge_listener.jsx — loaded into a running After Effects session. Opens a TCP socket on 127.0.0.1:45445 and evaluates whatever ExtendScript it receives, using a simple length-prefixed JSON protocol.

  2. IPC_Client (a real dependency, not vendored code) — the Python-side client for that same protocol (execute_job()).

  3. ae_bridge_mcp/eval.pyevaluate() tries the socket transport first, falling back to macOS AppleScript (osascript) if the listener isn't loaded or AE isn't reachable. No CLI of its own — see AE_Eval for that.

  4. ae_bridge_mcp/server.py — the actual MCP server: a stdio JSON-RPC loop exposing 4 tools (see below) to any MCP client.

Tests in tests/.

Tools exposed over MCP

Tool

What it does

ae_eval_script

Evaluate arbitrary ExtendScript, return the result or error.

ae_get_active_comp

Name, id, width, height, fps, duration, layer count for the active comp.

ae_get_selected_layers

Index, name, label, comment, enabled, 3D, hasVideo for every selected layer.

ae_get_layer_properties

Transform (position/scale/rotation/opacity/anchor), sourceRect, and applied effects for a given 1-based layer index.

Setup

1. Load the AE-side listener

In After Effects: File → Scripts → Run Script File... and pick ae_bridge_mcp/jsx/ae_bridge_listener.jsx. It starts listening immediately ($.global.AEBridgeListener.start() runs at the bottom of the file) and writes a confirmation line to the ExtendScript console.

To have it load automatically, drop it (or a script that $.evalFile()s it) into AE's Scripts/Startup folder for your AE version, or wire it into whatever your product's own panel/launcher already loads at boot.

To stop it: $.global.AEBridgeListener.stop(); from the ExtendScript console, or just close AE.

2. Install the Python package

pip install -e .
# or, for running tests too:
pip install -e ".[dev]"

One runtime dependency: IPC_Client (itself pure stdlib, no further dependencies) — installed automatically from its pinned commit via the git+https://... URL in pyproject.toml.

3. Point an MCP client at it

Example config (Claude Code, or any MCP client using the same command/args shape):

{
  "mcpServers": {
    "ae-bridge": {
      "command": "python3",
      "args": ["-m", "ae_bridge_mcp"]
    }
  }
}

Or run it directly to sanity-check it starts: python3 -m ae_bridge_mcp (it will sit waiting for JSON-RPC on stdin — that's expected, it's not meant to be run interactively).

This repo has no standalone CLI of its own by design — for a terminal command with the same evaluate/active-comp/selected-layers capability, install AE_Eval instead (or alongside — they share the same AE-side listener).

Transport fallback

evaluate() tries, in order:

  1. TCP socket (127.0.0.1:45445) — needs ae_bridge_listener.jsx loaded in AE. Sub-5ms round-trip. The connect phase fails fast (connect_timeout, default 2s) so an unloaded listener falls through to AppleScript quickly; the response wait itself honors the caller's full requested timeout — a legitimately slow script (many layers/effects, a render trigger) isn't cut short at 2s.

  2. AppleScript (osascript, macOS only) — works even without the listener loaded, at the cost of a slower round-trip (writes a temp .jsx file, has AE run it via DoScriptFile, reads a temp JSON output file back).

If neither works, evaluate() returns a structured error rather than hanging — check that After Effects is running and the listener is loaded.

Security

ae_bridge_listener.jsx is an unauthenticated socket — any local process that can reach 127.0.0.1:45445 and speaks the wire protocol can execute arbitrary ExtendScript through it (including filesystem access and system.callSystem()-style OS command execution) for as long as After Effects is running with the listener loaded. This is inherent to what the tool does — an AI agent is meant to evaluate arbitrary ExtendScript through it — but it's worth knowing plainly before loading it on a shared machine (a render farm node, CI runner, or multi-user workstation), where any other local process/user could reach the same port.

Testing

pip install -e ".[dev]"
pytest tests/

Tests mock the AE-facing boundary (execute_job, eval_extendscript_socket, eval_extendscript_applescript) — they don't require a real After Effects instance to run.

Known limitations

  • ae_get_layer_properties reads position/scale/etc. via .value, which reads the property at the current playhead for keyframed properties, not a deterministic rest pose. If you need a deterministic read for an animated property, evaluate a custom script via ae_eval_script instead (e.g. prop.valueAtTime(prop.keyTime(1), true)).

  • The AppleScript fallback is macOS-only; on Windows/Linux, only the socket transport is available, so ae_bridge_listener.jsx must be loaded for anything to work at all.

  • ExtendScript's return is illegal outside a function body — if you're calling ae_eval_script with a multi-statement script that needs to return a value, wrap it in an IIFE yourself: (function(){ ...; return x; })(). eval.py's own wrap_iife() helper does this for the built-in structured tools.

Changelog

  • Hardened handle_call_tool so a malformed tool argument (e.g. a non-numeric layer_index) returns an isError result instead of raising and killing the persistent stdio server process.

  • Fixed evaluate() silently capping the entire socket-transport wait at 2 seconds regardless of the requested timeout — only the connect phase is capped now (connect_timeout), the response wait honors the real request.

  • Switched to depending on the IPC_Client package instead of a vendored copy (also picks up its connect-timeout and message-size-cap fixes).

  • Fixed packaging: ae_bridge_listener.jsx moved inside the package directory and is now actually included in a built wheel/sdist — a real pip install previously shipped a package with no way to reach After Effects on Windows/Linux (only worked by accident via editable installs from a live git checkout).

  • Removed the redundant ae-eval console-script entry point (it collided with the standalone AE_Eval package's own ae-eval command if both were installed together) and the CLI code it pointed to, which fully duplicated AE_Eval.

License

MIT — see LICENSE.