creatomate-mcp-server
by WAR10CK222
README.md
# Creatomate MCP Server
A production-grade **Model Context Protocol** server for [Creatomate](https://creatomate.com). It lets AI agents produce **professional videos and images** and **keep track of everything they publish**.
It runs on the 2026-07-28 MCP spec (stateless Streamable HTTP, multi-round-trip elicitation) with backwards-compatible serving for 2025-era clients, and ships with Docker. It also includes an agent **skill and Claude Code plugin**, and an **agent-driven eval suite**.
> **Why not just Creatomate's hosted MCP?** The official server exposes 8 thin CRUD tools. This one is built around what teams do every day:
> - a **design composer** with professional, safe-zone-aware recipes
> - a **linter** for the silent failures Creatomate's validator accepts
> - **visual previews** the agent can see
> - an **asset ledger** with full-text search: Creatomate has no API to list past renders, and its files expire after 30 days
> - **brand kits**
> - **cost guardrails with user confirmation**
> - **variants and batches**
> - prompts, resources and completions
>
> The eval suite measures both servers head to head.
---
## Contents
- [Quick start](#quick-start)
- [Connect a client](#connect-a-client)
- [What agents get](#what-agents-get)
- [Authentication & security](#authentication--security)
- [Configuration](#configuration)
- [Agent skill & Claude Code plugin](#agent-skill--claude-code-plugin)
- [Evals](#evals)
- [Development](#development)
- [Architecture](#architecture)
---
## Quick start
Requirements: Docker. Node 22.13+ is needed only for local development.
```bash
cp .env.example .env
# edit .env:
# CREATOMATE_API_KEY=<your project API key> (Creatomate → Project Settings → API)
# MCP_AUTH_TOKENS=me:<long random token> (node -e "console.log(require('crypto').randomBytes(24).toString('base64url'))")
docker compose up -d --build
curl http://127.0.0.1:8787/healthz # {"status":"ok",...}
```
- The MCP endpoint is `http://127.0.0.1:8787/mcp`.
- The port is published on localhost only.
- The ledger lives in the named volume `ledger-data`.
## Connect a client
**Claude Code: plugin (MCP connection + skill in one step)**
```bash
claude plugin marketplace add WAR10CK222/creatomate-mcp-server # or a local path to this repo
claude plugin install creatomate@creatomate-mcp # prompts for URL and token (stored in the keychain)
```
**Claude Code: MCP only**
```bash
claude mcp add --transport http creatomate http://127.0.0.1:8787/mcp --header "Authorization: Bearer <token>"
```
**Claude Desktop**, via `mcp-remote` in `claude_desktop_config.json`:
```json
{ "mcpServers": { "creatomate": { "command": "npx", "args": ["-y", "mcp-remote", "http://127.0.0.1:8787/mcp", "--header", "Authorization:Bearer ${CREATOMATE_MCP_TOKEN}"], "env": { "CREATOMATE_MCP_TOKEN": "<token>" } } } }
```
**VS Code:** `.vscode/mcp.json` is preconfigured and prompts for the token.
**Claude Agent SDK**
```ts
import { query } from '@anthropic-ai/claude-agent-sdk';
for await (const m of query({
prompt: 'Make a 15s Instagram Reel announcing our cold brew launch',
options: {
mcpServers: { creatomate: { type: 'http', url: 'http://127.0.0.1:8787/mcp', headers: { Authorization: `Bearer ${process.env.CREATOMATE_MCP_TOKEN}` } } },
allowedTools: ['mcp__creatomate__*'],
},
})) { /* … */ }
```
Then try the **`capabilities_tour`** prompt (≈2 credits), or ask for any video.
## What agents get
### Tools (14)
| Tool | Purpose | Notes |
|---|---|---|
| `creatomate_workspace_overview` | One-call context: brand kits, campaigns, ledger health, spend by campaign/day/template/platform/status/actor, budget, rate-limit headroom, key health | read-only |
| `creatomate_docs` | RenderScript knowledge base (15 guides): elements, timing, animations, captions, audio/TTS, templates, layout, cost, pitfalls | read-only |
| `creatomate_compose` | Brief → complete professional RenderScript for routine content. 6 recipes (`promo_scenes`, `captioned_clip`, `slideshow`, `quote_card`, `thumbnail`, `countdown`) × 5 layouts (`lower-third`, `centered`, `split`, `full-type`, `collage`, or `auto` mixing them per scene) × 6 art-direction styles (`bold`, `editorial`, `minimal`, `playful`, `cinematic`, `tech`). A seeded `variation` (random by default) means two runs of the same brief don't converge, and a returned seed reproduces a design. Brand kits win; safe zones and vmin/% units keep it resizable | read-only, free |
| `creatomate_validate` | Creatomate dry run + a 46-rule linter (untracked layers, elements after the end, non-built-in fonts, JPG-by-default, 60 fps trap, broken caption links, unreachable URLs, safe zones, contrast, reading speed …) + cost estimate | read-only, free |
| `creatomate_preview` | Renders key frames as small JPGs and **returns them as images** the agent can see | ≈1 credit/frame |
| `creatomate_render` | Renders and records the asset. Supports `variants[]` (aspect ratios, A/B tests, data rows) as one batch, lineage via `from_asset_id`, progress notifications, idempotency, budget checks, confirmations | spends credits |
| `creatomate_search_assets` | Full-text and faceted search of the ledger (on-screen text, color words, campaign, tags, status, approval, aspect, dates, expiry) | read-only |
| `creatomate_get_assets` | Status, URLs, source and history; imports external render ids; waits for pending renders with progress | read-only |
| `creatomate_update_assets` | Title, campaign, tags, approval, notes; bulk edits by filter with `expected_count` | idempotent |
| `creatomate_list_templates` / `creatomate_get_template` | Templates, their modifiable fields, and integration snippets (Node SDK, fetch v2, curl) | read-only |
| `creatomate_save_template` / `creatomate_delete_template` | Create, update or delete templates (updates and deletes need confirmation) | destructive |
| `creatomate_save_brand_kit` | Persist colors, fonts, logo and tone | idempotent |
Every tool has:
- a title, annotations and a description written for agents
- zod-validated input
- compact JSON results (also returned as `structuredContent`)
- `resource_link`s for assets
- actionable errors in the form `{kind, hint, fix}`
### Resources (with completion)
| Resource | Contents |
|---|---|
| `creatomate://guide` | How to use this server |
| `creatomate://docs/{topic}` | The 15 guides |
| `creatomate://catalog/{animations,easings,fonts,platforms,caption-styles,providers,stock-assets,recipes}` | Reference catalogs |
| `creatomate://examples/{name}` | 8 validated golden RenderScripts |
| `creatomate://templates/{id}` | Templates |
| `creatomate://assets/{id}` | Ledger records |
| `creatomate://campaigns/{name}` | Campaign summaries |
| `creatomate://brand-kits/{name}` | Saved brand kits |
| `creatomate://sources/{ref}` | Stored sources |
### Prompts (ready-made workflows, with argument completion)
| Prompt | Use |
|---|---|
| `social_video` | Short-form social video |
| `captioned_clip` | Captioned clip |
| `image_creative` | Single image design |
| `resize_for_platforms` | Resize an asset for other platforms |
| `template_from_brief` | Build a template (developers) |
| `batch_from_data` | One render per data row |
| `campaign_report` | Campaign summary |
| `capabilities_tour` | Guided tour of the server |
### User interaction & utilities
- **Confirmations.** Renders above `CONFIRM_ABOVE_CREDITS`, batches above `CONFIRM_BATCH_ABOVE`, template updates and deletes, and bulk ledger edits ask the user.
- Through **elicitation**: a form dialog via 2026 multi-round-trip `input_required`, or the legacy shim for older clients.
- Through a **signed `confirm_token`** when the client can't show dialogs.
- Tokens are HMAC-bound to the exact arguments, and an explicit *decline* is final.
- **Progress notifications** while waiting for renders.
- **Cancellation** stops waiting; the render itself continues.
- **Idempotency:** identical render calls within 2 minutes are de-duplicated.
- **Cost guardrails:** an estimate on every validate and compose, `max_credits` per call, `HARD_MAX_CREDITS_PER_RENDER`, `DAILY_CREDIT_BUDGET`.
- **Rate limiting:** an adaptive client-side limiter tracks Creatomate's `X-RateLimit-*` headers (30 requests per 10 s per account) and retries with `Retry-After`.
- **Webhooks** (when `PUBLIC_BASE_URL` is set): `/webhooks/creatomate/<token>` updates the ledger. Set it as the project's default webhook to capture renders made elsewhere (dashboard, Zapier, n8n).
### The asset ledger
Creatomate cannot list past renders, and it deletes files after 30 days. The server records every render in SQLite with an FTS5 index. Each record holds:
- **Descriptive:** title, campaign, tags, brief, notes, brand kit.
- **Content:** on-screen text, color words ("teal", "navy"), fonts.
- **Output:** format, size, aspect, duration.
- **Cost:** estimated and actual credits.
- **Status:** render status, approval, expiry.
- **Lineage:** parent asset, batch.
- **The exact source**, so expired assets can be re-rendered.
The CLI (`docker compose exec creatomate-mcp node dist/cli.js <cmd>`) offers `stats`, `export`, `alias` (move history after a key rotation), `webhook-url` and `tenant-id`.
## Authentication & security
| Mode | Client sends | Use |
|---|---|---|
| `AUTH_MODE=static` (default) | `Authorization: Bearer <token from MCP_AUTH_TOKENS>`; the server uses its own `CREATOMATE_API_KEY` | Single team / local Docker. The token name is recorded as the actor on each asset. |
| `AUTH_MODE=passthrough` | `Authorization: Bearer <Creatomate project API key>`, **the same credential the official `creatomate` Node SDK uses** | Multi-tenant. Keys are verified with a free dry run, cached by HMAC, and never stored or logged. Each project gets an isolated ledger. |
Hardening:
- Host and Origin validation (DNS-rebinding protection).
- The port is bound to `127.0.0.1`.
- The container runs non-root, read-only, with `no-new-privileges` and all capabilities dropped.
- Credentials are redacted in pino logs.
- **SSRF-guarded URL checks:** public http(s) only, private and loopback IPs blocked after DNS, no redirects.
- Ledger text is labelled as data, guarding against prompt injection; this is covered by an eval.
- claude.ai web connectors need public HTTPS and OAuth, which is out of scope. Use Claude Code, Desktop, VS Code or the SDKs.
## Configuration
See [`.env.example`](.env.example). Key variables:
| Variable | Default | Purpose |
|---|---|---|
| `AUTH_MODE` | `static` | `static` or `passthrough` |
| `CREATOMATE_API_KEY` | – | Project key (static mode) |
| `MCP_AUTH_TOKENS` | – | `name:token,…` (static mode) |
| `SERVER_SECRET` | auto (persisted in `DATA_DIR`) | HMAC for tenant ids, confirmation tokens, webhooks |
| `PORT` / `HOST` | `8787` / `127.0.0.1` | `HOST=0.0.0.0` in Docker |
| `ALLOWED_HOSTS` / `ALLOWED_ORIGINS` | – | Extra hostnames, e.g. behind a reverse proxy |
| `PUBLIC_BASE_URL` | – | Enables webhooks |
| `CONFIRM_ABOVE_CREDITS` | `20` | Confirmation threshold |
| `CONFIRM_BATCH_ABOVE` | `5` | Batch-size confirmation threshold |
| `HARD_MAX_CREDITS_PER_RENDER` | `600` | Hard cap per render |
| `DAILY_CREDIT_BUDGET` | `0` (off) | Daily budget across renders |
| `DEFAULT_WAIT_SECONDS` / `MAX_WAIT_SECONDS` | `45` / `90` | How long tools wait for renders |
| `URL_CHECKS` | `true` | Check media URLs before rendering |
| `DISABLED_FEATURES` | – | `compose,lint,brand_kits` (ablations) |
## Agent skill & Claude Code plugin
The `plugin/` folder is a Claude Code plugin, published through the repo-root `.claude-plugin/marketplace.json`. It contains:
| Path | What |
|---|---|
| `.mcp.json` | Connects the server; the URL and token come from plugin `userConfig`, and the token is stored in the keychain |
| `skills/creatomate-mcp/SKILL.md` | A router skill modeled on the Remotion and HyperFrames skills: preflight → start from state → route by deliverable → production checklist → non-negotiables → tool table |
| `references/*.md` | 13 references: setup, scene planning, creative direction (routine vs hero pieces), RenderScript, motion, captions/voiceover, typography/layout, brand kits, templates, variants/batches, asset library, cost/safety, pitfalls |
| `examples/` | Synced golden sources (`npm run skill:sync`) |
| `scripts/verify-output.mjs` | ffprobe checks + frame extraction |
| `evals/evals.json` | Trigger and behaviour evals for the skill |
Skill only, for other agents: `npx skills add WAR10CK222/creatomate-mcp-server --skill creatomate-mcp`.
## Evals
The evals are agent-driven: real Claude Code, headless, using only the MCP tools, against an isolated eval server (`docker compose --profile eval`). There are 26 everyday scenarios:
- content quality (Reels, captions, quote cards, thumbnails, real-estate video, multi-format, countdowns, revisions)
- template work
- asset tracking (find, failures, spend, fuzzy recall, bulk approve, expiry, import)
- safety (cost confirmation, declines, 429 recovery, broken URLs, prompt injection)
- knowledge
Checks are programmatic: ledger, Creatomate API, ffprobe. T1 adds an Opus vision judge and a blind pairwise comparison against Creatomate's official MCP.
Latest held-out comparison (15 briefs never used for tuning, checks frozen, Official able to read its guide):
- Both servers delivered every brief.
- This server won the blind head-to-head 8–4 with 3 ties, and averaged 3.23 vs 2.96 in judge quality. That is a consistent lead, but a modest one.
- Official remains stronger on high-energy sale designs.
- This server used about 2.5× fewer tool calls and 3.5× less agent spend, but about 2× the Creatomate credits, because of its preview frames.
Details and the fairness audit are in [`docs/eval-results.md`](docs/eval-results.md).
See [`evals/README.md`](evals/README.md) and the latest results in [`docs/eval-results.md`](docs/eval-results.md).
```bash
npm run eval -- --tier T0 --arm full # free (simulated renders)
npm run eval -- --tier T1 --arm full,full-skill,official # real renders + judge
```
## Development
```bash
npm install
npm run dev # tsx watch (reads .env)
npm run typecheck
npm test # 96 unit + integration tests (in-process MCP client, fake Creatomate API)
npm run build # dist/
npm run smoke # end-to-end against a running server (MCP_URL, MCP_TOKEN; --render spends ~4 credits)
npm run test:live # dry-run every golden example and compose recipe against the real API (free)
npm run gen:examples && npm run skill:sync
```
```
src/
index.ts, cli.ts, config.ts
http/ app (Express via @modelcontextprotocol/express), auth, webhooks
mcp/ server factory, instructions, confirm (elicitation/token), pipeline, refresh, resources, prompts, tools/*
creatomate/ typed v2 fetch client, errors, adaptive rate limiter, simulator
renderscript/ lint (+ SSRF-safe url checks), compose recipes, timeline, cost, extract, colors, modifications
ledger/ node:sqlite + FTS5 schema, repository
knowledge/ kb/*.md, catalogs/*.json (platforms, animations, 1,187 fonts, caption styles, providers, stock), examples/*.json
plugin/ Claude Code plugin + creatomate-mcp skill
evals/ harness, scenarios, fixtures, reports
test/ unit + integration
docs/ spike findings, eval results
```
Dependencies:
| Group | Packages |
|---|---|
| MCP | `@modelcontextprotocol/server`, `/express`, `/node` (v2) |
| Runtime | `express` 5, `zod` 4, `pino`, `ulid` |
| Dev | TypeScript 7, Vitest 5 |
The deprecated `creatomate` SDK is replaced by a typed v2 client that uses the same Bearer authentication.
## Architecture
```
Client ──POST /mcp (Bearer)──► Express (Host/Origin checks) ─► auth (static | passthrough) ─► tenant
└► createMcpHandler: fresh McpServer per request (stateless; 2026-07-28 + legacy 2025 clients)
├ tools / resources / prompts / completions
├ pipeline: resolve source → Creatomate dry run → lint → cost → confirm → render
├ Creatomate v2 client (rate limiter, retries, timeouts)
└ ledger (SQLite WAL + FTS5) ◄── webhooks (optional)
```
Design notes from the spikes: [`docs/spikes.md`](docs/spikes.md).
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues