# Motion Canvas Agent

> Agent tooling for Motion Canvas — seek, screenshot, scene graph inspection, settings control, and rendering via HTTP API. Requires a browser with the editor open. Use when this capability is needed.

- Skill: `tomevault-io/motion-canvas-agent` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add tomevault-io/motion-canvas-agent`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomevault-io/motion-canvas-agent/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: tomevault-io (https://skillmd.com/u/tomevault-io)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/tomevault-io/motion-canvas-agent

---


# Motion Canvas Agent Plugin

Full programmatic control of a running Motion Canvas editor via HTTP. Seek to any frame, capture screenshots, inspect the scene graph, change resolution/fps/background, and trigger rendering — all without browser automation.

## Architecture

```
Agent (curl/script)  ──HTTP──▶  Vite Server (agent-plugin.ts)  ──HMR WS──▶  Browser (agent-client.ts)
                                      │                                           │
                                      ▼                                           ▼
                                screenshots/                               Player + Renderer
                                (PNG files)                                + ProjectMetadata
```

## Setup

### 1. Copy plugin files into your project

- `assets/agent-plugin.ts` → project root
- `assets/agent-client.ts` → `src/`

### 2. vite.config.ts

```ts
import {defineConfig} from 'vite';
import motionCanvas from '@motion-canvas/vite-plugin';
import {agentPlugin} from './agent-plugin';

export default defineConfig({
  plugins: [
    motionCanvas(),
    agentPlugin({screenshotDir: './screenshots'}),
  ],
});
```

### 3. project.ts

```ts
import {makeProject} from '@motion-canvas/core';
import {agentClient} from './agent-client';

export default makeProject({
  plugins: [agentClient()],
  scenes: [...],
});
```

### 4. Start + open browser

```bash
npm start
# Open http://localhost:9000 in a browser
```

## HTTP API Reference

Base URL: `http://localhost:9000/__agent`

### Playback Control

| Method | Endpoint | Body | Response |
|--------|----------|------|----------|
| `GET` | `/status` | — | `{connected, frame, duration, fps, paused, sceneName, errors}` |
| `POST` | `/seek` | `{frame: 120}` | `{ok, frame}` |
| `POST` | `/next-frame` | — | `{ok, frame}` |
| `POST` | `/prev-frame` | — | `{ok, frame}` |
| `POST` | `/play` | — | `{ok}` |
| `POST` | `/pause` | — | `{ok}` |

### Screenshots

| Method | Endpoint | Body | Response |
|--------|----------|------|----------|
| `POST` | `/screenshot` | `{name: "my-shot"}` | `{ok, path, frame}` |
| `POST` | `/screenshot-base64` | — | `{ok, data, frame}` |

### Settings

| Method | Endpoint | Body | Response |
|--------|----------|------|----------|
| `GET` | `/settings` | — | `{background, range, size, previewFps, previewScale, renderingFps, renderingScale}` |
| `POST` | `/settings/background` | `{color: "#0F172A"}` | `{ok}` |
| `POST` | `/settings/range` | `{start: 0, end: 300}` | `{ok}` |
| `POST` | `/settings/size` | `{width: 1920, height: 1080}` | `{ok}` |
| `POST` | `/settings/preview-fps` | `{fps: 30}` | `{ok}` |
| `POST` | `/settings/preview-scale` | `{scale: 0.5}` | `{ok}` |
| `POST` | `/settings/rendering-fps` | `{fps: 60}` | `{ok}` |
| `POST` | `/settings/rendering-scale` | `{scale: 2}` | `{ok}` |

### Rendering

| Method | Endpoint | Body | Response |
|--------|----------|------|----------|
| `POST` | `/render` | `{fps?, range?}` | `{ok, message}` |
| `POST` | `/render/abort` | — | `{ok, message}` |

### Scene Info

| Method | Endpoint | Body | Response |
|--------|----------|------|----------|
| `GET` | `/scenes` | — | `{scenes: [{name}], currentScene}` |
| `GET` | `/scene-graph` | — | `{scene, graph}` (recursive node tree) |
| `GET` | `/threads` | — | `{scene, slides: [{name, id}]}` |

### Errors

| Method | Endpoint | Body | Response |
|--------|----------|------|----------|
| `GET` | `/errors` | — | `{errors: [{message, stack}]}` |
| `POST` | `/clear-errors` | — | `{ok}` |

## Scene Graph Format

`GET /__agent/scene-graph` returns a recursive tree:

```json
{
  "scene": "my-scene",
  "graph": {
    "type": "View2D",
    "key": "my-scene/View2D[1]",
    "position": {"x": 960, "y": 540},
    "size": {"width": 1920, "height": 1080},
    "children": [
      {
        "type": "Rect",
        "position": {"x": 0, "y": 0},
        "size": {"width": 400, "height": 300},
        "fill": "#1E293B",
        "children": [
          {
            "type": "Txt",
            "text": "Hello World"
          }
        ]
      }
    ]
  }
}
```

Each node includes: `type`, `key`, `position`, `size`, `opacity` (if not 1), `fill`, `text` (if applicable), and `children`.

## AI Agent Workflow

```bash
# 1. Check connection
curl -s localhost:9000/__agent/status

# 2. Set resolution
curl -s -X POST localhost:9000/__agent/settings/size \
  -H "Content-Type: application/json" -d '{"width": 1920, "height": 1080}'

# 3. Seek to a frame
curl -s -X POST localhost:9000/__agent/seek \
  -H "Content-Type: application/json" -d '{"frame": 60}'

# 4. Screenshot
curl -s -X POST localhost:9000/__agent/screenshot \
  -H "Content-Type: application/json" -d '{"name": "check-1"}'
# → Read ./screenshots/check-1.png

# 5. Inspect scene graph
curl -s localhost:9000/__agent/scene-graph

# 6. Check which scene we're in
curl -s localhost:9000/__agent/scenes

# 7. Check errors
curl -s localhost:9000/__agent/errors

# 8. Change background
curl -s -X POST localhost:9000/__agent/settings/background \
  -H "Content-Type: application/json" -d '{"color": "#000000"}'

# 9. Trigger render
curl -s -X POST localhost:9000/__agent/render
```

## Security

- **Localhost only**: The agent API runs on the Vite dev server which binds to localhost by default. Do not expose it to the network (avoid `--host 0.0.0.0` in production)
- **Screenshot paths**: The screenshot name is sanitized — path separators and dots are stripped, and the resolved path is verified to stay inside the configured `screenshotDir`
- **Scene graph text**: Text content extracted from the scene graph is sanitized (control characters stripped, truncated to 100 chars). Treat scene graph data as untrusted when processing it

## How It Works

- **Runtime plugin** (`agentClient()`) uses `Plugin.player()`, `Plugin.renderer()`, and `Plugin.project()` callbacks to capture the Player, Renderer, and Project instances
- **Player** provides seek, frame stepping, playback control, current scene, duration
- **ProjectMetadata** provides `shared` (background, range, size), `preview` (fps, scale), and `rendering` (fps, scale, exporter) settings via `.get()` / `.set()`
- **Renderer** provides `render(settings)` and `abort()` for export
- **Scene graph** is traversed via `scene.getView().peekChildren()` recursively, serializing type, position, size, fill, text, opacity
- **Communication** uses Vite's HMR WebSocket

## Files

| File | Copy to | Purpose |
|------|---------|---------|
| `assets/agent-plugin.ts` | Project root | Vite plugin — HTTP endpoints + WS relay |
| `assets/agent-client.ts` | `src/` | Runtime plugin — Player/Renderer/Project control + canvas capture |

---
> Source: [VideoZero/skills](https://github.com/VideoZero/skills) — distributed by [TomeVault](https://tomevault.io).
<!-- tomevault:4.0:skill_md:2026-06-19 -->

