MotionKit MCP Server
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@MotionKit MCP ServerValidate and render the video spec in spec.json"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
MotionKit
A programmable, branded video engine exposed through MCP. An AI agent describes a video in natural language, an agent layer turns that into a structured, versioned Video Specification (Zod schema), and MotionKit validates and renders it deterministically via Remotion.
The video engine ships in phases via OpenSpec changes (see CLAUDE.md for the build plan and
openspec/ for specs/proposals). Phase 1 (Foundation) is in: a versioned Video Specification
schema, a non-throwing structured-error validator, and a deterministic Remotion render pipeline
for the 16:9/9:16 formats, with motionkit validate/motionkit render CLI commands driving
them. Phase 2 (Brand System) adds a Brand system — a design-token document (colors, typography,
logo, spacing, shadows, and per-primitive style presets) referenced by id and never inlined into
a Video Specification — plus scene-level caption, frame, and logo fields, and three new
transitions (slide-left, slide-right, zoom) alongside the existing fade. Phase 3a
(Responsive Motion) adds a scene-level motion field (horizontal_pan/vertical_pan/zoom/
static, with an optional focal point) that drives real crop/pan/zoom render math based on each
source asset's actual dimensions, a frame: "phone" chrome decoration alongside "browser",
and the 1:1 format. Phase 3b (Overlays / PIP) adds a scene-anchored overlays array — a video
bubble (pip) rendered on top of its target scene, with a required audio: "own" | "muted" mode
covering the common "no separate A-roll, just a narrating webcam bubble over B-roll" pattern —
closing out Phase 3. Phase 4 (MCP tool surface) adds validate_video and render_video to the
MCP server alongside the existing ping — stateless, whole-Video-Specification-in/whole-result-
out wrappers around @motionkit/core's validate()/render(), so an MCP client can build and
render a video through the protocol MotionKit exists to serve, not just via the CLI. See
Using the MCP server below.
Packages
packages/core(@motionkit/core) — shared Zod schemas, types, error classes, utils, and the MotionKit engine: the Brand schema and registry (src/brand/), the Video Specification schema (src/video-spec/), its validator (src/validation/), and the Remotion render pipeline (src/rendering/).packages/mcp(@motionkit/mcp) — the MCP server AI agents talk to:ping(health check),validate_video, andrender_video. See Using the MCP server.packages/cli(@motionkit/cli) — an oclif CLI, installable globally from this checkout (npm install -g ./packages/cli) as themotionkitcommand:motionkit validate <spec.json>,motionkit render <spec.json>, andmotionkit mcp-configto register@motionkit/mcpwith an MCP-aware client. Seepackages/cli/examples/for runnable examples: a Phase 1 spec, a brand/caption/frame/logo/transition-demonstrating Phase 2 spec, a motion/phone-frame/1:1-demonstrating Phase 3a spec, and an overlays/PIP-demonstrating Phase 3b spec.
Related MCP server: Cutible MCP Server
Requirements
Node.js >= 22 (see
.nvmrc)
Getting started
npm install
npm run build
npm run lint
npm run typecheck
npm testnpm install at the repo root is the only install step — this is an npm workspaces monorepo, so
packages/cli and packages/mcp automatically get their @motionkit/core dependency symlinked
to packages/core rather than fetched from a registry.
Writing a Video Specification
A Video Specification (a spec.json) is the structured, versioned document that describes a
video as data — an output format, a frame rate, an ordered timeline of scenes, and optionally a
brand and a set of overlays. It's the contract between the creative/AI layer and MotionKit's
render pipeline. The authoritative shape is the Zod schema at
packages/core/src/video-spec/schema.ts (scenes) and packages/core/src/brand/schema.ts
(brand tokens) — everything below is grounded in those two files.
Top-level fields
Field | Type | Notes |
|
| The schema version literal. A future version adds a new literal, not a mutation of this one. |
|
| Output aspect ratio. |
| positive number | Frame rate. |
| string, optional | A brand id, resolved at validate/render time. Defaults to |
| array of scenes, min 1 | The ordered timeline. Required. |
| array of overlays, optional | Scene-anchored layers rendered on top of the scene timeline — a sibling array, not nested in |
One structural rule lives at the document level rather than per-scene: the first scene may not
declare a transition — there's nothing before it to transition from.
Scenes: a_roll and b_roll
scenes is a discriminated union on type: "a_roll" (primary footage carrying its own audio)
or "b_roll" (supporting footage). Both share the same base fields:
Field | Type | Notes |
| non-empty string | Path to the source video, resolved relative to the spec's own directory. |
| number | Seconds. Must be positive — enforced semantically by |
|
|
|
| string, optional | Text overlaid for the scene's duration, styled from the active brand's caption tokens. Must be non-empty if present. |
|
| A static, decorative chrome wrapper around the scene's visual content. |
|
|
|
| discriminated union, optional | Semantic pan/zoom/crop intent — see below. Omitted = a fixed, centered cover-crop. |
| non-negative number, optional | Where in |
| non-negative number, optional | An optional bound on where in |
b_roll scenes additionally take:
Field | Type | Notes |
|
| Defaults to |
a_roll scenes have no audio field — they always carry their own audio.
sourceStartSeconds/sourceEndSeconds let one source file be reused across multiple scenes, or
as both an A-roll and a later PIP overlay, without pre-cutting it by hand — point different
scenes/overlays at different offset ranges within the same asset. sourceEndSeconds > sourceStartSeconds
is checked structurally by the schema; whether the requested range actually fits the asset's real,
measured length (and is long enough to cover duration) can only be checked at render time (it
needs an ffprobe shell-out validate() deliberately never performs) — a range that doesn't fit
refuses the render with a SOURCE_RANGE_EXCEEDS_ASSET_DURATION error rather than silently
freezing the last frame or dropping audio.
motion: pan/zoom/crop intent
motion is a discriminated union on type, independent of frame (usable with or without one).
Every variant is schema-.strict(), so putting a direction on zoom/static (which don't
accept one) is a validation error rather than something silently dropped:
{ "type": "horizontal_pan", "direction"?: "left_to_right" | "right_to_left" }— direction defaults to"left_to_right".{ "type": "vertical_pan", "direction"?: "top_to_bottom" | "bottom_to_top" }— direction defaults to"top_to_bottom".{ "type": "zoom" }{ "type": "static" }
Every variant also accepts an optional focalPoint: { x, y }, normalized 0–1 within the
source asset (bounds checked semantically, so an out-of-range value is a reported error, not a
crash) — where a pan biases its crop, or what a static/zoom centers on. The actual
scale/translate math for a given frame lives in packages/core/src/rendering/cropTransform.ts
and uses each asset's real probed dimensions (probeAssetDimensions.ts), not just the target
composition's.
Overlays: the pip type
overlays is a sibling array to scenes, discriminated on type (only "pip" exists today —
a video bubble, the "webcam-narrating-over-B-roll" pattern). Each overlay is scene-anchored, not
time-anchored — it references a scene by index, not an absolute time range:
Field | Type | Notes |
| integer | Index into |
| non-empty string | Path to the overlay's video, resolved relative to the spec's own directory. |
| placement, optional | Defaults to the active brand's |
|
| Defaults to |
|
| Defaults to |
|
| Required. |
| non-negative numbers, optional | Same semantics as a scene's own fields above (see that row), applied to the overlay's own |
How brand resolves
brand is just a string id. At validate/render time, findBrand/loadBrand
(packages/core/src/brand/registry.ts) resolve it by checking, in order:
<specDir>/brands/<id>.brand.json— next to the spec file itself.The package's built-in
packages/core/src/brand/brands/— which ships exactly one brand,"default".
An unresolvable id is a structural BRAND_NOT_FOUND validation error listing the ids actually
available in both locations. A brand document (brandSchema in
packages/core/src/brand/schema.ts) carries: colors (primary/secondary/background/
text/accent), typography (fontFamily + sizes.title/subtitle/caption/cta), logo
(asset + defaultPosition — the logo's asset path resolves relative to the brand file's
own directory, not the spec's), spacing/borderRadius (sm/md/lg px scales), shadows
(sm/md/lg CSS box-shadow strings), captionStyle, browserFrameStyle,
phoneFrameStyle, pipStyle (a size scale plus borderWidth/borderColor/shadow/
defaultPosition), and defaultTransitionDurationSeconds. (titleStyle, lowerThirdStyle, and
ctaStyle are validated/loaded too, but nothing renders them yet — no title/lower-third/CTA
scene field exists.) A brand always lives in its own <id>.brand.json file — never inlined into
a Video Specification.
A complete worked example
This composes most of the vocabulary above into one spec: an explicit acme brand, a captioned
A-roll opener with a logo, a browser-framed B-roll with a pan and a PIP webcam bubble, a muted
B-roll with a phone frame and a zoom, and a closing A-roll.
{
"version": "1",
"format": "16:9",
"fps": 30,
"brand": "acme",
"scenes": [
{
"type": "a_roll",
"asset": "assets/interview.mp4",
"duration": 3,
"caption": "Welcome to Acme",
"logo": true
},
{
"type": "b_roll",
"asset": "assets/broll-1.mp4",
"duration": 4,
"frame": "browser",
"motion": { "type": "horizontal_pan", "direction": "left_to_right" },
"transition": { "type": "slide-left" }
},
{
"type": "b_roll",
"asset": "assets/broll-2.mp4",
"duration": 3,
"audio": "muted",
"frame": "phone",
"motion": { "type": "zoom", "focalPoint": { "x": 0.5, "y": 0.4 } },
"caption": "Now in 4K",
"transition": { "type": "zoom" }
},
{
"type": "a_roll",
"asset": "assets/interview-2.mp4",
"duration": 3,
"logo": { "position": "top_left" },
"transition": { "type": "slide-right" }
}
],
"overlays": [
{
"type": "pip",
"sceneIndex": 1,
"asset": "assets/webcam.mp4",
"position": "top_right",
"shape": "rounded_square",
"size": "lg",
"audio": "own"
}
]
}Note the first scene declares no transition (nothing to transition from), acme resolves from
a brands/acme.brand.json file that must sit next to this spec, and neither transition here sets
an explicit duration — both inherit acme.brand.json's defaultTransitionDurationSeconds.
More examples
Rather than guess at further permutations, read the runnable specs already in the repo — each is documented (feature-by-feature) in its own README:
packages/cli/examples/spec.json— Phase 1 only: plaina_roll/b_rollscenes, onefadetransition, nobrand(implicitly"default").packages/cli/examples/brand-spec.json— brand resolution,caption,frame: "browser", both forms oflogo, and all four transition types.packages/cli/examples/motion-spec.json— the1:1format and all fourmotiontypes (includingframe: "phone"combined with azoom).packages/cli/examples/overlays-spec.json— theoverlaysarray and thepiptype, including ab_rollscene with no A-roll behind it at all (audio carried entirely by anaudio: "own"PIP).packages/cli/examples/trim-spec.json—sourceStartSeconds/sourceEndSecondson both a scene and a PIP overlay, reusing one longer source file's middle/offset range instead of a hand-pre-cut clip.demos/fonoster-intro/spec-16x9.json(and itsspec-9x16.jsonsibling) — a full, real-world (non-synthetic-asset) spec: A-roll intro → PIP+B-roll → A-roll → PIP+B-roll, real brand colors, documented scene-by-scene indemos/fonoster-intro/README.md.
packages/cli/examples/README.md explains what each example spec is exercising and how to
render it; packages/cli/examples/generate-assets.sh synthesizes the placeholder video assets
those examples reference via ffmpeg.
Using the CLI
packages/cli (@motionkit/cli) is an oclif CLI wrapping
@motionkit/core's validate()/render() and the MCP client-registration helper below.
Install
npm install
npm run build
npm install -g ./packages/clinpm install -g ./packages/cli gives you a motionkit command on your PATH — install from the
local path (with the leading ./; a bare packages/cli is read as a GitHub shorthand and
fails), not by package name, since @motionkit/cli isn't published to the npm registry yet. Run
from the repo root inside this checkout, npm resolves the workspace-linked @motionkit/core
dependency automatically and symlinks the global motionkit binary straight back to
packages/cli — so it always runs whatever's currently built there. Rerun npm run build after
pulling changes to pick them up; no need to reinstall.
motionkit pingIf you'd rather not install globally, every command also runs via its built entry point directly:
node packages/cli/bin/run.js <command> [args] [flags](this is the pattern packages/cli/examples/README.md and demos/fonoster-intro/README.md use,
so their commands keep working without a global install.) The rest of this section uses the
global motionkit form; substitute either invocation freely.
Commands
Four commands exist today (packages/cli/src/commands/):
motionkit ping— health check; confirms the CLI wires up to@motionkit/core. No args.motionkit pingmotionkit validate <spec>— reads a Video Specification JSON file, runs@motionkit/core'svalidate(), and reports structured errors. Exits0on success,1on failure (scriptable).motionkit validate path/to/spec.jsonmotionkit render <spec>— validates first (so an invalid spec is never rendered, using the samevalidate()call and error formatting asmotionkit validate), then renders to MP4 via@motionkit/core'srender().motionkit render path/to/spec.json motionkit render path/to/spec.json --output out.mp4 motionkit render path/to/spec.json -o out.mp4The
--output/-oflag is optional — when omitted, the output MP4 is written alongside the spec file, same base name,.mp4extension (e.g.path/to/spec.mp4).motionkit mcp-config— registers the built@motionkit/mcpserver with an MCP-aware client's config file, so the client can launch MotionKit's MCP tools without hand-editing JSON.motionkit mcp-config motionkit mcp-config --client claude motionkit mcp-config --path /custom/config/location.json--clientdefaults toclaude(Claude Desktop) — the only client supported this phase, though the flag exists so more clients can be added later without a breaking CLI change. It resolves Claude Desktop's config file per OS (~/Library/Application Support/Claude/ claude_desktop_config.jsonon macOS,%APPDATA%\Claude\claude_desktop_config.jsonon Windows,~/.config/Claude/claude_desktop_config.jsonelsewhere — override with--path), merges in anmcpServers.motionkitentry that launchespackages/mcp/dist/index.jsvianode, and leaves every other key and every other registered server in that file untouched. It refuses to run (with a pointer tonpm run build) ifpackages/mcphasn't been built yet.
Being an oclif CLI, every command supports --help:
motionkit render --helpDuring active development on the CLI package itself, packages/cli's own npm run dev runs the
unbuilt TypeScript directly via tsx (watching src/ and re-executing bin/dev.js) — useful
when iterating on the CLI's source, not needed for ordinary day-to-day usage.
Using the MCP server
packages/mcp (@motionkit/mcp) is the MCP server AI agents talk to, over stdio. Register it
with an MCP-aware client via motionkit mcp-config (see above), or launch it directly:
node packages/mcp/dist/index.jsIt exposes three tools:
ping— health check; no input, returns{ ok: true, version: string }.validate_video— validates a Video Specification document without rendering, via@motionkit/core'svalidate(). Both tools deliberately leavespecas an opaque JSON value in their MCPinputSchema(notvideoSpecSchema's own shape) — the schema carries a document-level refinement the SDK's raw-shape input schema can't express, so real validation stays fully delegated to@motionkit/core, the single source of truth.Input
Type
Notes
specJSON value
The Video Specification document to validate.
specDirstring, required
Absolute path the spec's asset paths and
brandid resolve against.Returns the same
ValidationResultshape@motionkit/core'svalidate()produces:{ "valid": true }, or{ "valid": false, "errors": [{ "code", "message", "path"?, "suggestions"? }, ...] }listing every violation (not just the first).render_video— validates first (identical semantics tomotionkit render: an invalid spec is never rendered), then renders to MP4 via@motionkit/core'srender().Input
Type
Notes
specJSON value
The Video Specification document to render.
specDirstring, required
Absolute path the spec's asset paths and
brandid resolve against.outputPathstring, optional
Absolute path to write the MP4 to. Defaults to
<specDir>/output.mp4when omitted.Returns
{ "outputPath": string }on success, or the same{ "valid": false, "errors": [...] }shapevalidate_videoreports when the spec is invalid — no partial or corrupt file is written in that case.
For both tools, an invalid Video Specification is a normal, successful tool result (structured
errors in the response content), never an MCP-level isError: true — that's reserved for
genuine execution failures the caller can't fix by editing the spec (an unresolvable specDir,
or an unexpected render() exception such as an ffmpeg failure). Neither tool holds any
session state between calls: the caller passes the whole spec on every call and gets the whole
result back, consistent with MotionKit having no persistence layer of its own (see CLAUDE.md).
Workflow tooling
OpenSpec — spec-driven development. Specs live in
openspec/specs/, proposals inopenspec/changes/. Use/openspec:propose,/openspec:apply,/openspec:archive. Scaffolded vianpx @fission-ai/openspec@latest init --tools claude.psstack commands — this repo's
.claude/settings.jsonregisters thepsstackmarketplace and enables thepsplugin, so/ps:*commands (e.g./ps:ship,/ps:kaizen) are available without per-machine setup.
Scripts
Script | Description |
|
|
| ESLint (flat config) across the repo |
|
|
| mocha unit tests in every package |
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Flicense-qualityDmaintenanceEnables agentic vision loops for Remotion by providing tools to discover projects, list compositions, take screenshots, and render storyboards, returning images for model evaluation.
- Alicense-qualityBmaintenanceEnables AI agents to perform headless video editing through 35 tools for project creation, clip manipulation, rendering, quality control, and semantic search, all via JSON-RPC 2.0 over stdio.MIT
- Alicense-qualityAmaintenanceEnables agents to create, monitor, cancel, and download cinematic AI video renders through the Future Video Studio Agent API.MIT
- Alicense-qualityCmaintenanceEnables video editing via YAML/JSON edit specs, with validation and rendering using Remotion.44Apache 2.0
Related MCP Connectors
Create and manage cinematic AI video renders through the Future Video Studio Agent API.
Build and run visual creative-production workflows from your AI agent.
Generate images, video, music and voice from your CLI or AI agent. On-brand AI media toolkit.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/psanders/motionkit'
If you have feedback or need assistance with the MCP directory API, please join our Discord server