motion-mcp-server
This server lets you read, inspect, and edit Apple Motion template files (.motn/.moti) on disk, without needing a live Motion.app session.
Inspect — Open files to see canvas size, frame rate, duration; list all layers, text layers, their parameters, keyframes, and published (rig) parameters exposed to Final Cut Pro.
Animate — Change text content, set static parameter values (with optional force override for keyframed ones), set colors, create new keyframe curves from scratch, and add keyframes to existing animation curves.
Structure — Clone entire layer subtrees (with automatic ID remapping) to duplicate and rearrange elements.
Rig Management — Publish/unpublish parameters to control Final Cut Pro Inspector visibility.
Deliver — Save to a new file, open in Motion.app for visual review, and validate XML integrity.
Allows reading and editing Apple Motion template files (.motn/.moti), including inspecting and modifying layers, parameters, keyframes, and publishing settings, and saving the result to a new file.
Click on "Deploy 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., "@motion-mcp-server Open ~/Desktop/MyTitle.motn and list all layers"
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.
motion-mcp-server
An MCP server for reading and editing
Apple Motion (.motn / .moti) template files.
Scope — read this first
This server edits static template XML on disk. It does not, and cannot, drive a live Motion.app session.
Final Cut Pro exposes a rich AppleScript/Apple Events dictionary, which is
what makes a "live control" MCP server (see
fcp-mcp-server, the project
that inspired this one's architecture) possible for FCP. Apple Motion's own
scriptability is limited to three verbs: activate, open, quit. There is
no dictionary for pushing parameter values, scrubbing the playhead, or
reading back rendered frames from a running Motion document.
So this server's actual capability is: parse a .motn/.moti file (both are
the same ozml XML format Motion.app itself writes), let you inspect and
mutate its layer/parameter/keyframe/publish-settings tree, and save the
result to a new file. Getting the result on screen means opening the written
file in Motion.app yourself — motion_open_in_motion does that handoff — or
handing a published/rendered template to Final Cut Pro, which does have the
rich Apple Events surface (see the sibling fcpxml/commandpost MCP
servers for that half of a pipeline).
Related MCP server: fcp-mcp
What it can do
Five tool groups, 17 tools:
inspect —
motion_open,motion_list_layers,motion_list_text_layers,motion_get_parameter,motion_list_keyframes,motion_list_riganimate —
motion_set_text,motion_set_parameter_value,motion_set_color,motion_create_keyframe_curve,motion_add_keyframestructural —
motion_clone_layer(duplicate an existing<layer>subtree in place — see "Adding new layers" below)rig —
motion_publish_parameter,motion_unpublish_parameter(Final Cut Pro's Inspector reads these published/rig parameters when the template is used as an FCP title/generator/effect)deliver —
motion_save_as,motion_open_in_motion,motion_validate
Authoring motion from scratch
motion_create_keyframe_curve seeds a brand-new <curve> on a leaf
parameter that has never been keyframed in Motion — actual dynamic/
procedural animation authoring, not just editing curves Motion already
created. This was initially considered too risky to guess at (a curve's
type attribute is data-type-specific, and guessing wrong produces a file
Motion can't open), so it's grounded in a structural scan of ~4,700 real
Motion documents on the machine this was built on: every keyframed leaf
parameter observed — Position X/Y/Z, Scale, Angle/Rotation, Opacity, RGB(A)
color channels, behavior Amount parameters — used the same universal
scalar curve (type="1"), because Motion animates a compound property
(Position, Color, ...) by keyframing each numeric child parameter
independently rather than through one multi-component curve. A second curve
type ("0") does exist on some non-numeric/enum-like parameters (Random
Seed, Blend Mode, Interpolation) but never carried an actual keypoint in
that scan, so this tool deliberately doesn't attempt to synthesize it —
there's no real-file evidence for what a keyed type="0" curve should look
like. Verified against both the synthetic fixture and an ephemeral,
never-committed round-trip against a real local .motn file.
Once a parameter has a curve — whether Motion created it or
motion_create_keyframe_curve did — motion_add_keyframe appends further
keypoints to it, in time order.
Adding new layers
motion_clone_layer duplicates an existing <layer> subtree (including any
nested child layers, e.g. a Group's children) and inserts the copy as the
next sibling of the original. This is the supported way to add new
structure to a template — it's a clone, not a from-scratch synthesizer,
and that's a deliberate, evidence-based choice, not a missing feature.
A structural scan of real Motion documents (same discipline as the curve
work above) found two reasons hand-authoring a brand-new layer from scratch
is materially riskier than editing an existing one: a Text-layer scenenode
tree is far deeper than any parameter this server otherwise touches
(paragraph/scroll/crawl margins, per-run styles, cross-referencing <host>
links), and the numeric factoryID for the same semantic kind (e.g.
"Text") is not stable across documents — it varied across every
personal .motn/.moti file scanned. Hardcoding a factory id would
silently produce a file that opens fine in the document it was copied from
and fails to open, or resolves to the wrong factory, in another.
Cloning sidesteps both problems: the subtree being duplicated is already
valid, real, Motion-authored XML from the same document, so nothing about
its factory graph needs to be guessed. The only thing motion_clone_layer
actually computes is id remapping — every layer/scenenode id inside the
cloned subtree is reassigned to a freshly allocated, document-unique value
(scanned across the entire document, not just the subtree, so it can
never collide with anything already present), and any <host hostID="...">
link that points inside the cloned subtree is rewritten to match. A
<host> link pointing outside the clone — an intentional cross-reference
to an unrelated part of the document — is left untouched, since remapping
it would silently break that reference. Verified against the synthetic
fixture (including a nested-group case exercising both the internal- and
external-<host> fixup paths) and two ephemeral, never-committed
round-trips against real local .motn files — one a flat layer, one a
layer with a nested child.
The ozml schema
Ground truth for the tag shapes this server relies on was read directly from
real Motion.app output on the machine this was built on (Motion Projects/Autosave Vault/*.motn, Motion Templates.localized/*.moti), not
assumed. See the module docstring in motion/parser.py for the annotated
shape, including two nesting traps that are easy to get wrong by guessing:
Top-level layers are direct children of
<scene>, not<timeline>(<timeline>in this schema is only a UI display-state block).Text face/outline/glow color and font live under a
<style>element that's a sibling of a scenenode's<parameter>tree, not nested inside it.
No personal .motn/.moti file is committed to this repository. The test
suite runs against fixtures/sample.motn, a small hand-written synthetic
document that reproduces the same tag shapes.
Install
python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"Run the tests
.venv/bin/python3 -m pytest tests/ -vUse it from Claude
Add to your MCP client config (Claude Code's .mcp.json, or the Claude
desktop app's claude_desktop_config.json):
{
"mcpServers": {
"motion": {
"command": "/absolute/path/to/motion-mcp-server/.venv/bin/python3",
"args": ["/absolute/path/to/motion-mcp-server/server.py"]
}
}
}Typical workflow: motion_open a template → inspect with the inspect
tools → mutate with animate/structural/rig tools → motion_save_as a
new file (never overwrites the source implicitly) → motion_validate to
confirm the write round-trips → motion_open_in_motion for visual QC.
License
MIT — see LICENSE. Architecture informed by DareDev256's MIT-licensed
fcp-mcp-server; this is an
independent implementation for a different file format, not a fork.
Available Tools
16 toolsmotion_add_keyframeA
Append a keypoint to a parameter that is ALREADY keyframed in Motion (has an existing with at least one keypoint). If the parameter has never been keyframed at all, call motion_create_keyframe_curve first to seed its curve, then use this tool for every keypoint after that.
Inputs: path (required), value (required), scenenode_id (required), time_seconds (required), +2 more. Returns: object
motion_create_keyframe_curveA
Start animating a leaf parameter that has NEVER been keyframed in
Motion — creates its from scratch, seeded with one keypoint,
so you can author dynamic/procedural motion rather than only editing
curves Motion already created. Grounded in a structural scan of real
Motion documents: every keyframed leaf parameter observed (Position
X/Y/Z, Scale, Angle, Opacity, RGB(A) channels, behavior Amounts, ...)
uses the same universal scalar curve type — Motion animates compound
properties like Position or Color by keyframing each numeric child
parameter independently, so target one scalar channel at a time (e.g.
"Style/Face/Color/Red", not "Style/Face/Color"). Refuses a parameter
that's already keyframed (use motion_add_keyframe instead) or that's a
group with its own child parameters rather than a bare value.
default overrides the curve's rest value; it otherwise reuses the
parameter's current static value/default.
Inputs: path (required), value (required), scenenode_id (required), time_seconds (required), +3 more. Returns: object
motion_get_parameterA
Inspect a parameter by slash-separated name path from a scenenode, e.g. "Object/Text" or "Style/Face/Color/Red". Returns its current value/default, whether it's keyframed, and child parameter names if it's a group rather than a leaf.
Inputs: path (required), scenenode_id (required), parameter_path (required). Returns: object
motion_list_keyframesA
List every keypoint on an animated parameter's curve, in document order, with time (seconds) and value.
Inputs: path (required), scenenode_id (required), parameter_path (required). Returns: result
motion_list_layersB
List every layer in the template's layer tree (id, name, factory kind e.g. Text/Image/Emitter/Shape/Generator, nesting depth, locked).
Inputs: path (required). Returns: result
motion_list_rigA
List every published (rig) parameter — the controls Final Cut Pro's Inspector shows when this template is used as an FCP title/generator/ effect.
Inputs: path (required). Returns: result
motion_list_text_layersB
List every Text-factory layer with its current on-screen string and the scenenode id needed by motion_set_text.
Inputs: path (required). Returns: result
motion_openA
Open and index a .motn or .moti file. Required before any other tool can operate on it. Returns a summary (canvas size, frame rate, duration, layer/text-layer/published-parameter counts).
Inputs: path (required). Returns: object
motion_open_in_motionA
Open a file in Motion.app for visual review — this server cannot render frames or drive Motion's UI itself, so final visual QC always happens by handing the file to the app this way.
Inputs: path (required). Returns: object
motion_publish_parameterA
Publish an internal parameter as a rig control visible in Final Cut
Pro's Inspector when this template is used as an FCP title/generator/
effect. object_id and channel come from Motion's own object graph
(e.g. object="10003" channel="./1/100/101" for a layer's Position) —
inspect a similar existing published target with motion_list_rig on a
template you know works to find the right values for your layer.
Inputs: name (required), path (required), channel (required), object_id (required). Returns: object
motion_save_asA
Write the open, modified document to output_path. Never overwrites
the originally-opened file unless output_path is the same path — pass a
new filename to keep the source template untouched.
Inputs: path (required), output_path (required). Returns: object
motion_set_colorB
Set RGB(A) on a color parameter group, e.g. "Style/Face/Color" for a Text layer's face color (values are Motion's native 0.0-1.0 range, though Motion permits >1.0 for over-bright colors).
Inputs: red (required), blue (required), path (required), green (required), +3 more. Returns: object
motion_set_parameter_valueA
Set a leaf parameter's static value (e.g. "Object/Face/Opacity"). Refuses to touch an already-keyframed parameter unless force=True is passed explicitly, to avoid silently breaking an existing animation — use motion_add_keypoint for animated parameters instead.
Inputs: path (required), value (required), scenenode_id (required), parameter_path (required), +1 more. Returns: object
motion_set_textA
Change the on-screen string of a Text-factory layer. Font, color, and behaviors are untouched — only the text content changes.
Inputs: path (required), new_text (required), scenenode_id (required). Returns: object
motion_unpublish_parameterC
Remove a published (rig) parameter.
Inputs: path (required), channel (required), object_id (required). Returns: object
motion_validateA
Round-trip-check a file: re-parse it fresh from disk and confirm the root is a recognizable document. Useful right after motion_save_as to confirm the write didn't corrupt the XML.
Inputs: path (required). Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 16 tools
Each tool targets a distinct resource/action. The only potentially overlapping pairs (set_parameter_value vs set_color, create_keyframe_curve vs add_keyframe) are clearly separated by explicit use-case descriptions, leaving no ambiguity.
All tools share a motion_ prefix and use verb-first snake_case. Most follow verb_noun, but a few like motion_open, motion_save_as, and motion_validate deviate slightly from the noun-object pattern, creating minor inconsistency.
At 16 tools, the set is just over the ideal 3-15 range. The count is reasonable for a comprehensive Motion editing server, and each tool has a legitimate role, so it feels only slightly heavy.
The server covers the full editing lifecycle: open, list, inspect, modify, animate, publish, save, and validate. Minor gaps exist (e.g., no delete keyframe or enumerate all parameters), but core workflows are fully supported.
Maintenance
Related MCP Connectors
MCP server for Clipkit — gives AI agents a video toolbox via the Clipkit schema.
Hosted video MCP server for branded explainers and reference-driven edits.
MCP registry & directory: search, find & install 31k+ MCP servers & tools. Catalog and marketplace.
MCP server for the FFmpeg Micro video transcoding API — create, monitor, download transcodes.
Related MCP Servers
- AlicenseBqualityDmaintenanceAn unofficial MCP-compatible server that enables advanced automation, querying, and remote control of Adobe Premiere Pro projects for power users, workflow automation, and AI integration.1514MIT
- AlicenseBqualityCmaintenanceThe most capable MCP server for Final Cut Pro — 88 tools covering FCPXML editing, live FCP control, parametric puppets, and media analysis.88187 PyPI12MIT
- AlicenseNot gradedqualityDmaintenanceProvides an MCP server for programmatic reading, authoring, editing, designing, and optimization of Apple Keynote presentations.MIT
- AlicenseNot gradedqualityDmaintenanceMCP server that enables AI to control Final Cut Pro on macOS, including reading libraries/projects, importing FCPXML, and creating title or storyboard projects.MIT