Over HTTP

The same server runs on its own beside After Effects and serves several clients over Streamable HTTP. Each client presents a key of its own, which names it and says what it may do.

Starting it

mkdir -p ~/.config/s3l-mcp-aftereffects
echo '{ "Snail": { "Http": { "Token": "<a long random secret>" } } }' > ~/.config/s3l-mcp-aftereffects/config.json
chmod 600 ~/.config/s3l-mcp-aftereffects/config.json
export SNAIL_MCP_AFTEREFFECTS_TRANSPORT=Http
export SNAIL_MCP_AFTEREFFECTS_HTTP__URL=http://0.0.0.0:8080
dotnet Snail.MCP.AfterEffects.dll clients add titles --scope read,write,farm
dotnet Snail.MCP.AfterEffects.dll

The server refuses to start without a token: it would turn every client away while looking healthy. The token is the operator's key and holds every scope; clients add mints a key per client, printed once, and clients list and clients revoke <name> manage them. A revoked key is turned away on its next request, without restarting the server.

Kept running by launchd

A server that is meant to be there whenever After Effects is runs as a job of this Mac, not from a terminal. The template is deploy/s3l.mcp.aftereffects.plist; installing writes it with this machine's paths beside the publications:

dotnet publish src -c Release -p:Install=true

The install restarts the job when launchd already holds this very one, and otherwise prints the two commands that load it:

launchctl bootstrap gui/$UID ~/.s3l-mcp/Snail.MCP.AfterEffects/s3l.mcp.aftereffects.plist
launchctl kickstart -k gui/$UID/s3l.mcp.aftereffects

The job carries the transport and the address only. The operator's key stays in ~/.config/s3l-mcp-aftereffects/config.json, so no secret is in a file a backup may carry. Its log is ~/Library/Logs/Snail.MCP.AfterEffects.log. After Effects itself is not started by the job: the bridge in its Startup folder dials the server whenever a person opens the application.

Keys and scopes

A client connects to /mcp with Authorization: Bearer <key>. The key's scopes are checked before a call reaches After Effects, inside run_tools and run_program too, and over the file endpoints:

Scope Opens
read reading the project, the files, the diagnostics
write changing the project and writing files
delete removing items, layers and files, undo, cancelling renders, replacing files that exist
script ae_script, ae_run_menu_command, and the script command inside sequences and programs
farm ae_render, ae_render_in_media_encoder, ae_farm_submit and ae_farm_retry

A key minted without --scope gets everything but script. The call a key does not open is answered with a refusal naming the scope.

One tool list per client

Each client has a tool list of its own: a skill one client loads is in its list and nobody else's, and tools/list_changed reaches the client that holds a session. A client of the sessionless protocol revision is recognised by the name on its key.

Pages and files

/ shows the status (After Effects, the bridge, the farm, the storage, the server), and the areas files, captures, renders and jobs with previews, downloads, deletion and the cancelling of a running job; sign in with any key. /healthz answers anyone. /raw/<area>/<path> serves a file, or a folder as a tar or zip, and takes uploads into the files area, with a key or with a signed link that works for HTTP__LINK_MINUTES; ae_upload and ae_download hand such links out as shell commands for the client's machine.