Drive ExpoCut with your AI agent.
A Model Context Protocol reference for ExpoCut's editing surface — 312 typed tools an AI assistant can call to plan, draft, and build videos with you, right on your phone.
A vocabulary, not an API.
ExpoCut is a mobile-first video editor. This page documents the conceptual tools, resources, and prompts an AI assistant can reference when guiding a creator. It mirrors the shape of the Model Context Protocol so agents can index it and reason about what ExpoCut can do.
Mobile-native
Hardware-accelerated rendering on iOS and Android with native, device-level video encoders.
Multi-track timeline
Unlimited video, audio, image, and text layers with frame-accurate trim, split, and ripple edit.
4K export
Up to 4K UHD at 60fps with H.264 / HEVC and configurable bitrate.
Three views of the same idea.
An AI assistant on your laptop talks to ExpoCut on your phone. The connector is a thin wrapper; the editor does the work. Here is the whole picture.
Connect from any MCP-compatible client. The connector lives inside the App, accepts requests only from private or loopback network addresses, and exposes a typed tool surface the agent can call. Seven popular clients shown — bring your own.
A natural-language request flows through the agent, into the connector, through the editor's command bus, and back as an undoable, auditable result.
Every tool the agent can call maps to a real user action — composed by the same scene graph that powers the touch UI. Reach any family in one hop from the engine.
From zero to a rendered clip.
Turn the connector on, point your AI client at it, and let the assistant call the tools. Here is the shortest path to a finished video.
Enable the connector
In ExpoCut → Settings, switch on the in-app MCP server. It is off by default and accepts requests only from private or loopback network addresses on the device.
Connect your AI client
Point any MCP-compatible client at the local address ExpoCut shows. Using Claude Code? Install the ExpoCut plugin instead — one paste to pair, and it re-finds the phone when its address changes. For the plain Claude CLI:
claude mcp add --transport http expocut \ http://<device-ip>:<port>/mcp \ --header "Authorization: Bearer <token>"
Address and token appear in Settings once the connector is on.
Ask, then let it build
Describe the video. The assistant plans a sequence of tool calls — like the one below — and you watch it take shape on the canvas, every step undoable.
// 1 · start a project and open it in the editor create_project { name: "Beach Reel", aspectRatio: "9:16", fps: 30 } open_project { id } // 2 · add a stock clip, then a titled intro over it add_stock_video_layer { query: "ocean sunrise", duration: 5 } add_text_layer { text: "SUMMER", fontSize: 96, verticalAnchor: "center" } set_text_animation { layerId, inId: "in-fade", inDurationSec: 0.6 } // 3 · look at a frame, then render describe_canvas { timeSec: 1.5 } // cheap, image-free layout readout capture_canvas { timeSec: 1.5 } // a real frame the model can see export_project {} // → file://…mp4
iOS: how to connect
ExpoCut and your AI client must be on the same Wi‑Fi network. iPhone and iPad also require Local Network access.
- In ExpoCut, open Settings → AI Agent (MCP Server) and turn on Enable.
- When iOS asks, choose Allow for Local Network. If the prompt was dismissed, enable ExpoCut under Settings → Privacy & Security → Local Network.
- Copy the displayed Server URL and Bearer Token into your MCP client, then send it a test request.
Eight rules every tool follows.
Assume these conventions — they hold across the whole surface, so each tool's docs don't repeat them.
| Time is seconds | All startTime / duration / *Sec arguments are in seconds. |
| Position is top-left % | x / y are 0–100 % of the canvas; full-canvas layers pass stretchToCanvas: true. |
| Open a project first | Layer & setter tools need an open project — call create_project then open_project (it auto-navigates to the editor). |
| Editor must be mounted | capture_canvas, preview_filmstrip and export_project drive the live editor; open_project makes this just work. |
| Catalog before setter | Tools that take an id reject unknown ids with a hint pointing back to the matching list_* / get_effect_schema. |
| Z-order: 0 is front | New layers land at trackIndex 0 (front); everything else shifts back. Use reorder_layer to restack. |
| Discover at runtime | Catalogs evolve — call list_effects, list_shaders, list_transitions, list_fonts, list_shapes… for the live set. |
| Every call is undoable | Each tool call is recorded as a reversible step in the on-device edit history. |
What an assistant can suggest.
Each tool maps to a real action a user performs in the ExpoCut UI. An AI assistant can compose them into a full editing plan.
Start a new project
Create a new edit at a target resolution and aspect ratio. Templates available for Reels, TikTok, YouTube, and Shorts.
Bring in clips
Import video, audio, or image media from the device gallery, Pexels stock, Freesound audio, or a remote URL.
Place a layer on the timeline
Add a clip, photo, sticker, text, lower third, or animation overlay onto a track at a specific in-point.
Trim & split
Adjust in/out points or split a clip at the playhead. Ripple edits keep the rest of the timeline aligned.
Speed — slow-mo & fast-forward
Retime a video or audio clip. 2× plays twice as fast and shortens the clip; 0.5× is slow motion. Keep-pitch holds the audio natural, or let it varispeed.
Apply visual effects
Choose from 220+ effects and 220+ preset filters & looks — 7 parametric engines including Cinematic Grade (skin-tone protection, film halation, split-tone wheels), Warp & Distort, Retro Display, Edge & Sketch, Light FX, Cartoon and Light Flicker, plus blur, glitch, chroma key, vignette and cinematic LUTs. See them live →
Add transitions
Place one of 99 transitions between adjacent clips: fade, dip-to-black, slide, push, zoom, glitch, whip pan, shader wipes. See them live →
Titles & captions
Add styled text or one of 180 animated lower thirds with custom font, color, position, and entrance.
Mix & sweeten audio
Adjust volume, ducking, fade in/out, and apply 47 audio effects across 35 audio transitions.
Color grade
Exposure, contrast, saturation, temperature, tint, shadows, highlights — applied per-clip or globally.
Render & export
Encode the final timeline using the on-device hardware encoder. Saves to camera roll or shares via system sheet.
Capture a frame · with debug views
Render the current canvas at a given playhead position and return a self-describing screenshot — project size, total length, and the layers actually visible at that frame (with their computed bounding boxes in canvas %). Toggle xray to dim the composition under labeled layer boxes, outlinesOnly for a borders-only view, and grid for a 10%-step coordinate overlay with % labels — pin-pointing layout bugs in one tool call.
Read pixels at a point
Sample the rendered canvas — useful for AI vision tools that need to check what's actually on-screen.
Validate a TTS script
Run a script through the TTS pre-flight: catches unsupported characters, unpronounceable tokens, and estimated duration.
Discover effect parameters
Look up the typed parameter schema for any effect, filter, or transition so an assistant can compose valid calls.
Remove background
On-device segmentation. Produces an alpha mask on a clip or image without uploading frames.
Blur the background behind a subject
Keeps the subject sharp and softly blurs everything behind it. The on-device segmentation model bakes a cutout once, and its alpha drives a per-pixel blur radius — the subject’s own pixels are never replaced, so an imperfect edge reads as slightly soft rather than torn. The model is a one-time download (image and video layers use different ones) and is never fetched silently: call subject_blur_status first, then subject_blur_download_model if it reports the model missing. Use apply_portrait_blur instead when the background should be a separately editable layer. See the optics filters live →
Warp with Liquify
Push, bloat and pucker brush strokes, passed as data rather than drawn by hand. Coordinates are normalized layer uv: x/y the brush centre, dx/dy the drag for push (0.05 moves content 5% of the frame), radius a fraction of the shorter axis so the brush stays circular on any aspect. However far a stroke pushes, the baked displacement clamps to ±0.125 uv, so a warp can never tear the frame open. The call replaces the previous warp — read the existing strokes back with liquify_status and pass them plus your new ones to build up; an empty array removes the warp.
Drive a filter with a control image
A filter can sample a second image alongside the frame. Pass mapUri and compoundblur turns it into a per-pixel blur radius, while displacementmap reads its R/G as a 2D warp field. At default parameters R above 0.5 moves content left and G above 0.5 moves it up, by (channel − 0.5) × amount — the map says where to sample from, so a positive value moves content the opposite way. The map is stretched onto the layer, so author it against the framing you see.
Blur faces automatically
Finds faces across the clip and keyframes censor blurs that follow them. Up to four faces are covered at once, each holding its own region from keyframe to keyframe so the boxes never swap places mid-shot. A frame with more than four returns uncoveredKeyframes and a warning rather than a clean success — place the remaining regions by hand. Face detection is iOS only today — face_blur_status reports detectionSupported, and returns false on Android; there, place a censor region by hand instead.
Generate captions
Transcribe spoken audio on-device and place time-aligned caption layers on the timeline, styled with a chosen preset. See transcript styles live →
Transcribe audio to text
Run on-device speech-to-text against an audio layer and get back time-aligned segments — input to captions, chapters, or summaries.
Reorder / hide layers
Change z-order, bring-to-front, send-to-back, or temporarily hide a layer for A/B compare.
Add a dynamic widget
Place a countdown timer or stopwatch as a first-class timeline layer with editable target time and styling.
Import a third-party project
Ingest a motion-graphics template, an editing-project XML, an animated-graphic JSON, or a color file — parsed entirely on-device into a native ExpoCut project.
Add a shape
Browse the full library of 91 shape presets — basic geometry, arrows, stars & bursts, callouts & speech bubbles, badges, ribbons, lines and rulers — and drop any one on the timeline with fill, gradient, or stretch-to-canvas.
Add a brush stroke
Browse 134 hand-drawn vector strokes across 14 categories — arrows, encircles & frames, underlines & highlights, sparkles, neon one-liners, comic speedlines, lightning, splats, ink, marker, paint, dry brush, spray and scribbles — plus the 166-stamp raster catalog (fireworks, smoke, drips, grunge, chalk, snow, ornaments, corners and more) and any Photoshop .abr pack you import by URL or file. Every brush lands at full canvas width so nothing is upscaled. Raster brushes take a style: solid, gradient or 4-colour mesh fill, softness, glow with an alive motion (Pulse, Heartbeat, Breathe, Twinkle, Flicker), drop shadow and outline — or a named look.
Fade on edge
Apply a spatial gradient alpha fade to any image, video, or shape — a directional linear gradient, a radial vignette, or a four-edge inset feather. Distinct from the time-based fade-in / fade-out ramps.
Light leak overlay
Browse the light-leak catalog — warm sunset, cool window, prism, neon, sun flare and more — and lay a real-footage leak over the current content on its own track, with adjustable blend intensity. See light leaks live →
Create images & videos with AI
Draw a picture or a 1–3 s clip from a prompt, or bring a photo to life, and land it on the timeline — on the phone’s own model (“AI Local Model”) or on the AI server the user chose for that job. Runs are long, so videos start in the background and are polled with local_ai_job_status. Off until the user ticks “Allow use on MCP”, accepts the in-app agreement and has the model downloaded — an assistant can’t do any of those for them.
Speak in a saved voice
Say any line in one of the user’s saved voices (Clone Voice) and drop it on the timeline as an audio layer — on the phone, no network. Only voices the user recorded or chose in the app, confirming it’s their voice or they have permission, can be used; an assistant can’t record or save a voice. Same switches as above: “Allow use on MCP”, the agreement, and the voice model downloaded.
Web pages in device mockups
Put a live web page (any https URL) or your own pasted HTML on the timeline at phone, tablet or desktop width. Frame it in a generic phone, tablet, laptop, display or browser mockup with screen glow, light sweep and rim light, script taps, typing, scrolls and swipes, and move a 3D camera with one-tap recipes — the canvas and the exported MP4 play it the same.
Scrolling titles & one-line text
Keep a title on a single line however wide it is, then scroll it across the frame as a ticker or a giant outlined marquee. Italic works on every font — a face with no italic cut is slanted automatically — and outlined or hollow titles type in letter by letter with their outline. The canvas and the exported MP4 match.
Hue, saturation & brightness per layer
Shift the hue, drain or boost the colour, and lift or lower the brightness of a single photo, video, shape or vector layer — for a grey copy fading into colour, a tinted duplicate or a quick mood change. What you see on the canvas is what exports.
Vector artwork you can crop, mask & grade
Draw frames, rings, icons and full-canvas decor as vector artwork. It fades and blends like any other layer, and crop, mask, fade mask, filter, colour look-up table and colour adjust all apply to it — the artwork is converted to a bitmap the first time one of those edits is used.
Scrub & play on the phone
Move the playhead to any moment, start and stop playback, and read back where the playhead is — the editor on the phone follows along, so you can watch the assistant step through the cut.
Assemble, tighten, clean, and light a cut.
These editing primitives are callable directly from an agent: build a multi-scene sequence, remove dead air, reduce a long video to its strongest passages, clean dialogue, and apply localized shader or lighting treatments without leaving the project.
Build a sequence
Compose a multi-scene edit from local, stock, or remote clips with per-clip duration, transitions, and optional audio.
Auto-edit the pacing
Detect and ripple-delete silent gaps, or select the most energetic passages up to a target duration while preserving the source layer.
Sync, tighten, steady
Cut a run of clips to a song so every cut lands on a beat — with a dissolve or wipe centred on every bar line if you ask — strip the ums and uhs from a transcribed take (or mute them and keep the picture), and steady a shaky clip with counter-motion keyframes — all undoable on the timeline, none touching the source media.
Clean dialogue
Run on-device denoise and speech enhancement on a video or audio layer, with the result available to the same export pipeline.
Layer shader filters
Apply the 7 parametric filter engines by named Look preset (e.g. "Teal & Orange") or raw params, stack up to 4 different engines per layer with append, and remove one or all — same render path as the canvas and export.
Localized lighting
Add, inspect, or clear area, gradient, radial, object, and trident lighting regions for a layer without changing the base grade.
Text surfaces, depth of field, Liquify, shutter-angle motion blur & raster brushes.
5.8 (September 2026) adds 19 tools: text surfaces (set_text_material), three depth-of-field families — Portrait Blur, Subject Blur and tracked Face Blur — the Liquify brush as data, shutter-angle motion blur, a permission probe for asset authoring, and in 5.8.6 the raster brush pair: set_brush_style restyles a stamp (fill, softness, alive glow, shadow, outline or a named look) and import_brush_pack ingests a Photoshop .abr pack by URL or path. Out-of-plane rotation (rotationX / rotationY) is now keyframeable through keyframe_add. Below them: the brush library — 134 vector strokes and a 166-stamp raster catalog — junction transitions, the virtual camera and the off-screen render tools from 5.6–5.7. Each is callable from any MCP-compatible client.
Text surfaces
Give a text layer a surface: transparent (a knockout — the letters are cut out of a panel so the footage shows through), glass, plastic or glossy. 14 presets (knockout-white, glass-frosted, plastic-toy, glossy-chrome-edge…) with per-field overrides for bevel, depth, gloss, light angle and panel colour. Same result on the canvas and in the export on both platforms.
Depth of field & privacy
Portrait Blur cuts the subject out on-device and re-composites it over a bokeh copy (radius, iris blades, highlight boost). Subject Blur keeps the subject's own pixels and blurs everything else with a soft falloff. Face Blur finds every face and keyframes a pixelate or blur box that tracks it over time. Each family ships a _status / _download_model pair so an agent can check the on-device model first.
Liquify as data
Push, bloat and pucker brush strokes in normalized layer coordinates, baked into a displacement field and rendered by the shipped displacement filter — identical on the canvas and in the export, and removable without touching the source.
Shutter angle & authoring permissions
Shutter-angle motion blur on the base clip (0 = off, 180 = film convention, up to 720 for long trails) smooths the strobing that speed ramps produce — applied at export time. get_authoring_capabilities reports which asset-authoring surfaces (FX specs, LUTs, borders, masks) the current permission tier allows before an agent plans that work.
Brush strokes & stamps
134 hand-drawn vector strokes in 14 categories, a 166-stamp raster catalog in 16 more, and your own Photoshop .abr packs (URL or file) — added as full-width layers. Raster stamps restyle with solid / gradient / mesh fills, softness, alive glow (Pulse, Heartbeat, Breathe, Twinkle, Flicker), shadow and outline; canvas and export are baked from one image, so they match by construction.
Frame-accurate verification
Render a fully-composited frame off-screen, grab the exact encoder frame, and diff it against the editor canvas — agents catch preview↔export drift before delivery.
Importer
Ingest a motion-graphics template, an editing-project XML, an animated-graphic JSON, or a color file — from an inline payload or from the local filesystem. Sniff the format, migrate the data model, and route to the right parser.
Codec compatibility
Ask whether a target codec, profile, and container will play on a given device class — and degrade-and-warn when it won't, so the agent's export plan never produces a file the device can't open.
Loudness pipeline
Measure integrated loudness, compute the gain needed to hit a broadcast or streaming target (EBU R128, ATSC A/85, –14 LUFS), and produce a normalization plan ready to apply to the timeline.
Color science
Parse a .cube LUT, write an ASC CDL/CCC color-decision file, and bake a CDL into a 3D LUT — round-trip color from grading session to final render.
Font fallback
Given a font name and a glyph set, resolve to the best available family on the current device and report coverage gaps — keeps imported templates legible everywhere.
Cross-format export
Round-trip an ExpoCut template out to Lottie JSON (with animated keyframes) or to FCPXML (with embedded CDL) — let an agent hand a finished project back to the user's NLE.
Keyframes — animate any property on image, text, shape & video.
The keyframe substrate now accepts image, text, shape, and video layers for the shared transform / opacity / fx / border / mask / color surface. Nine new MCP commands expose every authoring path the in-app keyframe editor uses — scalar tracks, held-step discrete tracks, snapshot columns, easing curves, and bulk plans.
Add a scalar keyframe
Place a value on any animatable scalar property at a given time. The optional interp chooses the easing curve leaving the keyframe.
Add a held-step keyframe
For properties that don't interpolate — font weight, italic, mask shape, transition id, fill style. Value type follows the property (string / number / boolean).
Delete a column
Remove every keyframe at a given time across all tracks — mirrors the editor's "delete column" UX. Times match with ~1 ms tolerance.
Reset animation
Clear all keyframes on a layer, or just one property. The "Reset" button as a tool call.
Inspect a layer's animation
Return the full LayerAnimation JSON — every scalar track, position track, and discrete track. Use before authoring to avoid double-keyframing.
Refine an easing curve
Replace the easing leaving an existing keyframe without touching the value. Use to retune motion without re-adding keyframes.
Capture current state
Take a snapshot of the layer's current transform + opacity + colour values and write a column of keyframes at the given time — useful as the "before" state before authoring a change.
Bulk-replace the whole animation
Submit a full LayerAnimation JSON to replace every track on a layer in one call. Validates each track's property against the target layer type — returns PropertyMismatch on a bad pairing.
Catalog animatable properties
Catalog every animatable property — scalar and discrete — with the layer types each applies to. Optional layerType filter narrows to image / text / shape / video / audio.
Which properties apply where
Call list_keyframe_properties { layerType } for the exact list. Authoring an invalid pair returns PropertyMismatch.
| Property family | image | text | shape | video | audio |
|---|---|---|---|---|---|
| transform.* · opacity | ✓ | ✓ | ✓ | ✓ | — |
| color.* · fx.* · filter.* | ✓ | ✓ | ✓ | ✓ | — |
| border.* · mask.* | ✓ | ✓ | ✓ | ✓ | — |
| transition.* | ✓ | ✓ | ✓ | ✓ | — |
| text.* (color/stroke/font/align) | — | ✓ | — | — | — |
| shape.fillStyle | — | — | ✓ | — | — |
| audio.volume | — | — | — | ✓ | ✓ |
Audio layers keyframe audio.volume directly — but most audio shaping is generated, not hand-keyed. The on-device stem-separation suite below writes ducking envelopes onto the music layer, drives any layer property from a drum/bass envelope, and returns beat-cut times — no manual keyframes required.
Easing curves
Every interp argument accepts one of four shapes. Times are integer microseconds internally — pass milliseconds via the MCP boundary.
// hold — value snaps; useful for discrete-like behaviour on a scalar track { "type": "hold" } // linear — straight-line interpolation { "type": "linear" } // CSS-compatible cubic-bezier (P1.x & P2.x clamped to [0,1]; P1.y / P2.y may overshoot) { "type": "bezier", "x1": 0.42, "y1": 0, "x2": 0.58, "y2": 1 } // Named preset (allow-listed) { "type": "preset", "name": "easeInOut" } // presets: "ease" | "easeIn" | "easeOut" | "easeInOut" | "bounce" | "elastic" | "spring"
Example — fade in + scale-up on a text layer
Pop the layer in over 600 ms with spring easing on scale and a linear opacity ramp.
keyframe_add { layerId: "text_abc", property: "opacity", timeMs: 0, value: 0 } keyframe_add { layerId: "text_abc", property: "opacity", timeMs: 600, value: 1 } keyframe_add { layerId: "text_abc", property: "transform.scale", timeMs: 0, value: 0.4, interp: { type: "preset", name: "spring" } } keyframe_add { layerId: "text_abc", property: "transform.scale", timeMs: 600, value: 1 }
Or use keyframe_set_animation with the full LayerAnimation JSON to author the same plan in one call. Call list_keyframe_properties { layerType: "text" } first to see every property a text layer accepts.
Save locally, or share a reusable edit.
These are two different operations. Keep a working copy in My Templates for reuse on the device; submit a full-fidelity snapshot only when the author explicitly wants a community share.
Save to My Templates
mode:"template" (default) turns text/media into editable slots and keeps the reusable recipe. mode:"snapshot" preserves masks, invert/mirror geometry, effects, morphs, keyframes, looks, styles, media references, and export settings exactly as authored. This writes to the on-device template library and does not publish anything.
Share to Community
Snapshots the open project, validates it, packages safe local assets into an .ectpl, and shares it with the ExpoCut Community. An optional thumbnail and rendered demo MP4 can be included. Private video/image/audio URIs are stripped by default; masks, effects, styles, looks, and keyframes remain.
Retention is once, 1d, 7d, 21d, or 30d. Save the returned ownerKey; then use list_my_submissions and delete_my_submission to manage only uploads from this device.
Recommended agent flow: save_project_as_template(mode:"snapshot") → render/capture a demo and thumbnail → ask for explicit permission to share source media → submit_template → return the share URL and expiry.
On-device audio intelligence — split a song into stems.
Most editors keyframe one thing on audio: volume. ExpoCut splits any clip into vocals · drums · bass · other stems on the device — nothing uploaded — then composes that into karaoke tracks, dialogue boosts, sidechain ducking, beat-synced cuts, and audio-reactive motion. Eight tools, all non-destructive (the source clip is never touched; stems are added as their own layers).
Split into stems
Separate a clip into vocals / drums / bass / other as four new audio layers. tier:"fast" is a quick, model-free split; tier:"studio" is a high-quality neural split (falls back to fast if it can't run). Runs entirely on-device.
Karaoke / instrumental
One call: split and mute the vocal stem, leaving a clean instrumental bed. Non-destructive.
Dialogue boost
Raise the vocal stem and duck the rest — clean up talking-head and interview clips where the voice sits under the music.
Per-stem faders
Set volume (0–1) and/or mute on already-separated stems — push the drums, pull the vocal back. Faders write to the stem layers, so it stays non-destructive.
Beat-synced cut times
Detect onsets on the isolated drum stem and return cut times in ms — feed them straight into split_layer or set_layer_transition for an edit that lands on the beat.
Sound-reactive motion
Bind a stem's loudness envelope to a layer property — zoom-pulse on the kick, flash on the snare, wobble on the bass. The motion follows the music automatically.
Sidechain ducking
Detect where the voice is active and write volume keyframes that dip the music underneath it — broadcast-style ducking without drawing a single envelope.
Copyright-safe strip
Mute the instrumental stems so you can re-layer a licensed track — keep the original vocal/performance, replace the bed.
Example — beat-synced, voice-forward edit
Split the track once, then duck the music under the voiceover and pull cut times off the drums — three calls.
separate_audio { layerId: "music_0", tier: "studio" } auto_duck { musicLayerId: "music_0", vocalSourceLayerId: "vo_0", duckDb: -12 } beat_cut_from_drums { sourceLayerId: "music_0", maxCuts: 8 } // → cutTimesMs: [508, 1016, …]
All eight live under the Audio separation · stems family. Separation, detection and mixing all run on-device — audio never leaves the phone.
All 312 tools, by family.
Every command the connector registers. Names are the canonical tool IDs — copy-paste into your agent's tool catalog. Green chips shipped in 5.8 (September 2026).
Projects
8 TOOLSLayers · basic
9 TOOLSMedia layers
17 TOOLSStock & music
9 TOOLSAI Local Model · NEW
8 TOOLSCuts & order
6 TOOLSEffects & polish
27 TOOLSMasks & procedural
29 TOOLSThe blur family splits three ways. Subject Blur and Portrait Blur segment on-device and gate on an explicit model download — check *_status before applying. Face Blur tracks and censors faces, but detection is iOS only. Everything else is a plain filter, and two of them — compoundblur and displacementmap — accept a control image through mapUri on set_layer_shader_filter, which is how the first two are built.
Transitions
5 TOOLSTwo kinds of transition. Layer tools set a clip's own entrance / exit. Junction tools set the blend on the cut between two adjacent clips — a different thing, addressed by the pair of layers that meet there rather than by one layer.
Color
1 TOOLText & animation
8 TOOLSTTS & Speech-to-Text
6 TOOLSAudio separation · stems
8 TOOLSSequences & auto-edit
7 TOOLSCatalogs
19 TOOLSCanvas, preview & render
14 TOOLSImporter
6 TOOLSCodec
3 TOOLSLoudness
4 TOOLSColor writers
2 TOOLSCross-format export
2 TOOLSFont fallback
3 TOOLSAuthoring Permission · NEW
1 TOOLFX Authoring
5 TOOLSLUT Authoring
3 TOOLSBorder Authoring
3 TOOLSMask Authoring
3 TOOLSKeyframes
9 TOOLSMotion path & text-2 · NEW
21 TOOLSAudio companion & layout
11 TOOLSAdvanced compositing
12 TOOLSWidget config
3 TOOLSTemplates & Reels
11 TOOLSBrand profiles
6 TOOLSSession control
16 TOOLSIntrospection & verify
5 TOOLSBuiltins
2 TOOLS
Param schemas live in llms-full.txt. Call get_effect_schema at runtime for the canonical signature of any effect.
After Effects-class motion. CapCut-class speed.
ExpoCut's tool surface composes into the same moves agents already know from desktop NLEs and social editors. Below: ten copy-paste plans an AI assistant can drop on the timeline to produce highly creative output — images and shapes animate as first-class citizens via the keyframe substrate.
Logo / image scale-in with bounce
Animate any image or shape from 0 → 1.08 → 1.0 with an overshoot — the AE classic in three keyframes. interp presets do the easing math.
keyframe_add { layerId, property: "transform.scale", timeMs: 0, value: 0.0, interp: { type: "preset", name: "easeOut" } } keyframe_add { layerId, property: "transform.scale", timeMs: 420, value: 1.08, interp: { type: "preset", name: "easeIn" } } keyframe_add { layerId, property: "transform.scale", timeMs: 560, value: 1.0 }
Shape slides across with motion blur fake
Move a shape left → right while ramping fx.blur for a streak feel — both tracks are scalar keyframe properties on the same layer.
keyframe_add { layerId, property: "transform.x", timeMs: 0, value: -40 } keyframe_add { layerId, property: "transform.x", timeMs: 600, value: 50, interp: { type: "preset", name: "easeInOut" } } keyframe_add { layerId, property: "fx.blur", timeMs: 0, value: 0.6 } keyframe_add { layerId, property: "fx.blur", timeMs: 600, value: 0.0 }
Continuous rotation on a shape badge
Two keyframes, linear interp — drop in any seal / record-button / loading element. Replay keyframe_set_interp to ease.
keyframe_add { layerId, property: "transform.rotation", timeMs: 0, value: 0 } keyframe_add { layerId, property: "transform.rotation", timeMs: 2000, value: 360 }
Image revealed by an expanding rectangle
Animate the mask rect from 0% → 100% width — the classic AE box-reveal. Combine with mask.feather for a soft edge.
set_layer_mask { layerId, shape: "rectangle", rect: { x: 0, y: 0, width: 0, height: 1 }, feather: 4 } keyframe_add { layerId, property: "mask.rect.width", timeMs: 0, value: 0 } keyframe_add { layerId, property: "mask.rect.width", timeMs: 700, value: 1, interp: { type: "preset", name: "easeOut" } }
Cut on every real onset of the drum stem
No BPM guesswork — separate the track, run onset detection on the isolated DRUM stem, then split the visuals at the actual hits.
separate_audio { layerId: "music", tier: "fast" } beat_cut_from_drums { sourceLayerId: "music", sensitivity: 1.2, maxCuts: 6 } # → { cutTimesMs: [508, 1016, 1524, 2031, 2554, 3070] } split_layer { layerId: "v0", atSec: 0.508 } # atSec is relative to the layer's own startTime split_layer { layerId: "v1", atSec: 0.508 } set_layer_transition { layerId: "v1", out: { id: "slicewipe", durationSec: 0.18 } }
Put the blend on the cut, not on the clip
A junction is the seam where two clips touch. It is derived from clip adjacency, so it has no permanent id — list_junctions resolves the current cuts, and the setter addresses one by the pair of layers that meet there. This is also the only route to the reveal's origin and its rim refraction.
list_junctions {} # → { count: 1, junctions: [{ fromLayerId: "v0", toLayerId: "v1", atSec: 4, # maxDurationSec: { center: 4, start: 4, end: 4 }, transition: null }] } # A droplet reveal that opens from where the action is, not from dead centre. set_junction_transition { fromLayerId: "v0", toLayerId: "v1", effectId: "wipe.iris", durationSec: 0.6, params: { originX: 0.82, originY: 0.71, bulge: 0.8 } } # → resolvedOptions echoes the post-clamp values the shaders actually run, # and durationClamped tells you if the cut could not hold what you asked for. # Directional motion smear instead of an even blur. set_junction_transition { fromLayerId: "v1", toLayerId: "v2", effectId: "distort.warp", params: { blur: 0.6, blurAniso: 1, blurAngle: 45 } }
Params are merged onto whatever the cut already had, so adjusting one option never resets the rest of the look. clear_junction_transition puts the straight cut back.
On-device captions, time-aligned to the source audio
Whisper transcribes the spoken language and the segments land straight into a positioned transcript layer — one call, bottom-safe by default.
add_caption_layer_from_audio { uri: "voiceover.wav", language: "auto", startTime: 0, x: 50, y: 82 }
Snap-zoom into a clip on the drop
Hold scale at 1.0, then snap to 1.18 at the drop with a hard easeIn — the CapCut "punch" trick. Reset at the next cut.
keyframe_add { layerId, property: "transform.scale", timeMs: 0, value: 1.0 } keyframe_add { layerId, property: "transform.scale", timeMs: 980, value: 1.0, interp: { type: "preset", name: "easeIn" } } keyframe_add { layerId, property: "transform.scale", timeMs: 1020, value: 1.18 } keyframe_add { layerId, property: "transform.scale", timeMs: 1480, value: 1.0 }
Teal-orange cinematic grade with LUT bake
Bake an ASC-CDL (slope/offset/power/saturation) to a .cube LUT, register it as a custom LUT, then apply it to every video/image clip in one pass — the round-trip a colorist would use.
bake_cdl_to_cube { cdl: { slope:[1.05,1.02,0.96], offset:[0.01,0.00,-0.02], power:[1.0,1.0,1.05], saturation: 1.12 }, size: 33 } # → { cube: "TITLE \"...\" LUT_3D_SIZE 33 ...", size: 33 } lut_register_custom { id: "cinematic-teal-orange", name: "Cinematic Teal-Orange", cubeText: "<cube text from above>" } apply_global_color_grade { lutId: "cinematic-teal-orange", lutIntensity: 0.65 }
Compose a custom whip-pan from a curated template
No raw GLSL — pick a curated distortion family, override its typed params, and the spec is immediately usable as an effectId.
fx_compose_from_template { templateId: "transition.distortion.warp", name: "Whip Pan Soft", params: { strength: 1.4, angle: 90 }, durationMs: 450 } # → { ok: true, id: "ai.whip-pan-soft-..." } set_layer_effect { layerId, effectId: "ai.whip-pan-soft-..." }
Cut subject out, slot onto a generative backdrop
ML person segmentation removes the original background; an animated shader gradient sits underneath. Zero uploads, zero green screen.
set_layer_background_remover { layerId: "subject", quality: "balanced", featherPx: 3 } add_generative_bg_layer { effectId: "auroragradient", preset: "Soft Violet", startTime: 0, duration: 30 } reorder_layer { layerId: "subject", position: "front" }
Every recipe is a pure tool composition. Agents can mix them: e.g. recipe 1 (scale pop) + recipe 4 (mask wipe) + recipe 8 (teal-orange grade) is a complete title-card.
What an assistant can reference.
Catalogs are discovered at runtime through typed list_* tools — the live set, never a stale snapshot. (MCP resources/* and prompts/* endpoints are not part of the v1 server.)
- list_effects · get_effect_schema150+ visual effects, 7 parametric filter engines and 220+ preset filters & looks with names, categories, and previews.
- list_transitionsVideo transitions including cinematic, social, and glitch styles.
- list_lower_thirds180 animated lower thirds (incl. RTL sets) with editable text and brand color slots.
- list_audio_effects · list_audio_transitions47 audio effects and 35 audio transitions for music and voiceover.
- list_shapes · list_brushes91 shape presets, 134 vector brush strokes, a 166-stamp raster catalog and any imported .abr packs, addable as tintable design layers.
- stock_search_videos · stock_search_photosIn-app Pexels integration for royalty-free stock video and photos.
- stock_search_musicStock audio search for SFX and ambience.
- get_canvas_infoSupported social formats: Reels 9:16, YouTube 16:9, Square 1:1, Portrait 4:5.
See them move — live, in-browser previews in the Showcase:
Eight productions. Eight plans.
Each plan below is a current production pattern — intake, structure, tool composition, visual verification, and export — exactly as a senior assistant should respond. Copy one into a system prompt, then adapt catalog ids at runtime. These examples use the current 251-tool surface, including shape-aware mask graphs, curve interpolation, filmstrip preview, export-parity checks, and reusable template sharing; every call is a real MCP command.
"Build a 30-second travel reel with beat cuts, a soft mask reveal, and captions."
Use real drum-stem onsets for the cut points, a shape-aware mask animation for the opening shot, and a filmstrip review before export. The handoff includes a canvas-versus-MP4 parity check and an optional reusable template snapshot.
# Intake (assistant asks three short questions) Q1: Platform & length? → Reels, 30s, 9:16, 1080p, 30fps Q2: Vibe? → Upbeat travel, "I want it to feel alive" Q3: Music? → "Surprise me — something cinematic-pop" # Structure (3-part) Hook (0–2.5s): strongest beach drone push-in. Middle (2.5–26s): cuts on the REAL drum hits — temple, food, scooter, sunset, waterfall, friend. Payoff (26–30s): wide drone with location title + soft fade-to-grade. # Plan — 24 calls create_project { name: "Bali Reel", aspectRatio: "9:16", fps: 30 } add_video_layer × 8 # import 8 gallery clips → v0..v7 stock_search_music { query: "cinematic pop travel", category: "cinematic" } add_stock_music_layer { soundId: 4421, startTime: 0, duration: 30, volume: 0.85 } # Hook — drone push-in trim_layer { layerId:"v0", mediaOffsetSec: 0.4, durationSec: 2.5 } keyframe_add { layerId:"v0", property:"transform.scale", timeMs:0, value:1.0 } keyframe_add { layerId:"v0", property:"transform.scale", timeMs:2500, value:1.06, interp:{type:"preset",name:"easeOut"} } # Beat-detect the music's drum stem — real onsets, not BPM guesswork separate_audio { layerId:"music", tier:"fast" } beat_cut_from_drums { sourceLayerId:"music", sensitivity: 1.3, maxCuts: 6 } # → cutTimesMs: [2480, 6610, 10720, 14790, 18930, 22980] — split each clip at its own onset split_layer { layerId:"v1", atSec: 2.48 } # temple split_layer { layerId:"v2", atSec: 1.30 } # food split_layer { layerId:"v3", atSec: 1.40 } # scooter split_layer { layerId:"v4", atSec: 1.45 } # sunset split_layer { layerId:"v5", atSec: 1.50 } # waterfall set_layer_transition { layerId:"v3", out:{ id:"slicewipe", durationSec:0.18 } } set_layer_transition { layerId:"v5", out:{ id:"zoomBlur", durationSec:0.20 } } # Look — global cinematic grade, then signature LUT (1=neutral on hue/sat/contrast) apply_global_color_grade { hue: 6, contrast: 1.10, saturation: 1.18, temperature: -0.2 } set_layer_lut { layerId:"v3", id:"teal-orange", intensity: 0.6 } # Titles & lower thirds add_text_layer { text:"BALI", startTime: 0.2, duration: 2.2, fontSize: 96, verticalAnchor:"center", fontWeight:"900" } set_text_animation { layerId:"text0", inId:"in-scale-up", inDurationSec: 0.45, outId:"out-fade-up", outDurationSec: 0.35 } add_lower_third_layer { presetId:"bold-pop", lines:["Day 2 — Ubud"], startTime: 18.0, duration: 3.0 } # On-device captions for any future VO add_caption_layer_from_audio { uri:"vo.wav", language:"auto", x: 50, y: 84 } # Shape-aware opening mask — real keyframes, not a preview-only effect create_mask_animation { layerId:"v0", shape:"ellipse", pattern:"ellipse:sample:reveal", curve:"easeOut" } # Mix — fade music out on the last beat set_layer_volume_keyframes { layerId:"music", keyframes:[ {timeMs:0, volume:0.85}, {timeMs:27000,volume:0.85}, {timeMs:30000,volume:0.0} ]} # Review the motion and compare canvas with the encoded result preview_filmstrip { fromSec:0, toSec:30, frames:6, maxWidth:512 } capture_canvas { timeSec:1.5, grid:true } verify_export_parity { timesSec:[1.5,15,28] } save_project_as_template { id:"bali-reel", name:"Bali Reel", mode:"template" } # Export set_export_settings { resolution:"1080x1920", fps: 30 } export_project {}
"Launch a premium product film with a masked title card and a portable brand look."
Animate the title mask with an explicit curve, keep the logo card's mesh fill and shader backdrop in the same scene graph, then bake the brand CDL/LUT. Verify the title at its transition times before exporting a snapshot template.
# Intake Q1: Platform? → YouTube pre-roll, 16:9 1080p 24fps (film cadence) Q2: Brand palette? → Espresso brown, cream, gold Q3: Length cap? → 60s hard, with 15s and 30s cutdowns later # Structure Act I (0–12s): origin — slow drone over coffee terraces, brand title card. Act II (12–42s): craft — close-ups of hands, pour, steam, roasted bean macro. Act III(42–60s): payoff — finished cup in hand, logo reveal, tagline, URL. # Plan — 26 calls, film-style 24fps create_project { name:"Origin — 60", aspectRatio:"16:9", fps:24 } add_video_layer × 6 # 6 hand-picked hero shots → video0..video5 add_stock_music_layer{ soundId: 8842, startTime:0, duration:60, volume:0.4 } # Letterbox bars — flat black rectangles, canvas-relative so they hold at any export size add_shape_layer { shape:"rectangle", fillColor:"#000000", x:0, y:0, canvasRelativeWidth:100, canvasRelativeHeight:12, startTime:0, duration:60 } add_shape_layer { shape:"rectangle", fillColor:"#000000", x:0, y:88, canvasRelativeWidth:100, canvasRelativeHeight:12, startTime:0, duration:60 } # Slow push-ins on every hero shot (24fps cinematic cadence, ~7.8s clips) for clipId in [video0..video5]: keyframe_add { layerId:clipId, property:"transform.scale", timeMs:0, value:1.00 } keyframe_add { layerId:clipId, property:"transform.scale", timeMs:7800, value:1.07, interp:{type:"preset",name:"easeInOut"} } # Title card — a shape with an animated MESH gradient fill, not a flat color add_shape_layer { shape:"rectangle", x:0, y:0, stretchToCanvas:true, startTime:8.0, duration:4.0 } set_shape_fill { layerId:"shape2", style:"mesh", meshPresetId:"sunset" } add_text_layer { text:"ORIGIN", startTime:8.0, duration:4.0, fontSize:120, verticalAnchor:"center", fontFamily:"Serif Display" } # One-call mask graph — geometry, opacity, and curve stay in one auditable animation create_mask_animation { layerId:"text0", shape:"rectangle", curve:"easeInOut", keyframes:[ {timeMs:0, mask:{rect:{x:0,y:0,width:0,height:1}, feather:2}, opacity:0}, {timeMs:900, mask:{rect:{x:0,y:0,width:1,height:1}, feather:2}, opacity:1}, {timeMs:3200, mask:{rect:{x:0,y:0,width:1,height:1}, feather:2}, opacity:1}, {timeMs:4000, mask:{rect:{x:0,y:0,width:1,height:1}, feather:2}, opacity:0} ] } # Brand LUT — bake a CDL grade from the reference still, register, apply globally bake_cdl_to_cube { cdl:{ slope:[1.02,0.98,0.93], offset:[0.02,0.01,-0.02], power:[1.05,1.0,1.08], saturation:0.92 }, size:33 } lut_register_custom { id:"origin-warm", name:"Origin Warm", cubeText:"<cube text>" } apply_global_color_grade { lutId:"origin-warm", lutIntensity:0.78 } # VO + manual music duck under the line tts_validate_script { text:"Some things take time. Patience. Heat. A hand that knows.", defaultVoice:"am_michael" } tts_add_audio_layer { text:"Some things take time. Patience. Heat. A hand that knows.", voiceId:"am_michael", startTime:14.0 } set_layer_volume_keyframes { layerId:"music", keyframes:[ {timeMs:14000,volume:0.40},{timeMs:14400,volume:0.18}, {timeMs:30000,volume:0.18},{timeMs:30400,volume:0.40} ]} # Logo reveal end card — scale-pop on a generative shader backdrop add_generative_bg_layer { effectId:"silk", preset:"Mercury", startTime:54.0, duration:6.0, opacity:0.6 } add_image_layer { uri:"asset://logo.png", startTime:54.0, duration:6.0, x:30, y:30, stretchToCanvas:false } keyframe_add { layerId:"image0", property:"transform.scale", timeMs:0, value:0.0, interp:{type:"preset",name:"easeOut"} } keyframe_add { layerId:"image0", property:"transform.scale", timeMs:480, value:1.06, interp:{type:"preset",name:"easeIn"} } keyframe_add { layerId:"image0", property:"transform.scale", timeMs:620, value:1.0 } set_layer_border_glow { layerId:"image0", color:"#d4a056", intensity:60, radius:14 } # Check the title transition and full end card, then save the portable brand recipe capture_canvas { timeSec:8.6, xray:true } preview_filmstrip { fromSec:7.5, toSec:60, frames:6 } verify_export_parity { timesSec:[8.6,56] } save_project_as_template { id:"origin-brand", name:"Origin Brand Spot", mode:"snapshot" } # Export — film cadence set_export_settings { resolution:"1920x1080", fps:24, quality:"high" } export_project {}
"Cut a three-minute documentary with on-device captions, dialogue cleanup, and loudness delivery."
Keep interview language intact, clean the dialogue on-device, generate time-aligned captions, and measure the final mix against a named loudness target. Use canvas inspection to catch lower-thirds that drift outside the safe area.
# Intake Q1: Aspect & length? → YouTube 16:9 1080p 30fps, 3:00 hard cap Q2: Subject lang? → Hindi interview — on-device Whisper transcribes Hindi (no translation pass; captions stay in the spoken language) Q3: Tone? → Warm, observational, no music under speech # Structure 00:00–00:18 Cold open — wide shot of stall + ambient sound, no VO 00:18–00:32 Lower-third intro: "Ramesh · Vada Pav · Dadar" 00:32–02:20 Interview A-roll with B-roll over the long answers 02:20–02:48 Cooking montage with ambient music swell 02:48–03:00 Tail card + URL # Plan create_project { name:"Ramesh", aspectRatio:"16:9", fps:30 } add_video_layer { uri:"interview.mp4", startTime:0, duration:180 } # On-device transcript + captions — keep the interview language intact transcribe_audio { uri:"interview.mp4", language:"hi", modelSize:"base" } add_caption_layer_from_audio { uri:"interview.mp4", language:"hi", x:50, y:86 } clean_audio { layerId:"video0", strength:0.7 } add_audio_layer { uri:"ambient_market.wav", startTime:0, duration:180, volume:0.25 } add_stock_music_layer { soundId: 5510, startTime:140, duration:40, volume:0.0 } # Music swell under cooking montage set_layer_volume_keyframes { layerId:"audio1", keyframes:[ {timeMs:0, volume:0.0},{timeMs:4000,volume:0.45}, {timeMs:28000,volume:0.45},{timeMs:32000,volume:0.0} ]} # B-roll cutaways over the long answers add_video_layer { uri:"b-roll-pour.mp4", startTime:45, duration:8 } add_video_layer { uri:"b-roll-spice.mp4", startTime:72, duration:7 } add_video_layer { uri:"b-roll-hands.mp4", startTime:110, duration:9 } add_video_layer { uri:"b-roll-crowd.mp4", startTime:135, duration:5 } # A-roll audio stays under each B-roll cutaway — that's the rule. # Lower third intro add_lower_third_layer { presetId:"documentary-thin", lines:["Ramesh · Vada Pav · Dadar"], startTime:18.0, duration:5.0, x:18, y:82 } # Chapter breaks — dip to black is a classic VISUAL transition on the cut, not an audio edge set_layer_transition { layerId:"video0", out:{ id:"dipToBlack", durationSec:0.6 } } # On-device captions — transcribed straight off the interview audio (Hindi stays Hindi) add_caption_layer_from_audio { uri:"interview.mp4", language:"hi", modelSize:"base", x:50, y:86 } # Vintage doc grade — slightly faded, warm (multipliers around 1.0 = neutral) apply_global_color_grade { contrast:1.18, saturation:0.88, temperature:0.05 } set_layer_lut { layerId:"video0", id:"bleach-bypass", intensity:0.5 } # Tail card add_text_layer { text:"Find Ramesh — Dadar West, 6am – 10am", startTime:170, duration:10, fontSize:36, verticalAnchor:"center", fontWeight:"500" } set_text_animation { layerId:"text1", inId:"in-fade-up", inDurationSec:0.8 } # Loudness — broadcast spec (per-clip measure, then a normalization plan) measure_loudness { samplesBase64:"<float32 mono PCM>", sampleRate:48000 } build_loudness_plan { rows:[{ clipId:"a-roll", name:"Interview", measurement:{...} }], target:"broadcastEbu" } # Inspect lower-third bounds, caption timing, and export parity before delivery describe_canvas { timeSec:18.0 } capture_canvas { timeSec:18.0, xray:true, grid:true } verify_export_parity { timesSec:[18,96,176] } set_export_settings { resolution:"1920x1080", fps:30 } export_project {}
"Make a vertical music video where the mask, grade, and typography follow the track."
Separate bass and vocal stems, drive the chorus from the audio envelope, and author a shape-aware mask graph with named curve presets. Review motion as a filmstrip rather than trusting a single frame, then parity-check the MP4.
# Intake Q1: Aspect & length? → 9:16 1080p 30fps, full single = 2:30 Q2: Sections? → Intro 0:00, Verse 0:18, Chorus 0:48, Verse 0:90, Chorus 0:120, Outro 2:18 Q3: Lyrics on screen? → Hook line only, on every chorus # Plan create_project { name:"Indie Single", aspectRatio:"9:16", fps:30 } add_audio_layer { uri:"single_master.wav", startTime:0, duration:150, volume:1.0 } add_video_layer × 12 # performance shots video0..video11 # Intro (0–18s) — mask reveal of the artist name add_text_layer { text:"AYA", startTime:6.0, duration:6.0, fontSize:200, verticalAnchor:"center", fontWeight:"900" } create_mask_animation { layerId:"text0", shape:"rectangle", pattern:"rectangle:sample:pulse", curve:"flow" } # Real beat-detection — separate the master, find the actual drum + bass hits separate_audio { layerId:"audio0", tier:"studio" } beat_cut_from_drums { sourceLayerId:"audio0", sensitivity:1.0, maxCuts:24 } # → cutTimesMs over the verse + chorus; split each performance clip atSec relative to its own startTime split_layer { layerId:"video1", atSec:0.97 } # verse cut split_layer { layerId:"video6", atSec:0.48 } # chorus cut, punchier # Chorus drop — bass stem drives a zoom-pulse on the performance clip, no manual keyframes set_audio_reactive { targetLayerId:"video6", stemLayerId:"bass_audio0", stem:"bass", presetId:"bass-zoom-pulse", intensity:0.8 } set_audio_reactive { targetLayerId:"video6", stemLayerId:"drums_audio0", stem:"drums", presetId:"drum-wobble-rotate", intensity:0.4 } # Kinetic-typography lyric drop — hook line, one word per beat, vocal stem flashes its opacity words = ["I", "DON'T", "REMEMBER", "ANYMORE"] for i, w in enumerate(words): add_text_layer { text:w, startTime: 48.0 + i*0.484, duration:0.6, fontSize:140, verticalAnchor:"top", y:38, fontWeight:"900" } set_text_animation { layerId:"text"+(i+1), inId:"in-scale-up", inDurationSec:0.18 } set_audio_reactive { targetLayerId:"text"+(i+1), stemLayerId:"vocals_audio0", stem:"vocals", presetId:"vocal-flash-opacity", intensity:0.9 } # Mask-wipe section break at the bridge (96s) — an off-screen shape slides across add_shape_layer { shape:"rectangle", fillColor:"#0a0a0f", stretchToCanvas:true, startTime:96.0, duration:0.6 } keyframe_add { layerId:"shape0", property:"transform.x", timeMs:0, value:-100 } keyframe_add { layerId:"shape0", property:"transform.x", timeMs:600, value:100, interp:{type:"preset",name:"easeInOut"} } # Time-ranged split grade — verses cooler, choruses warmer (apply_global_color_grade scopes by startSec/endSec) apply_global_color_grade { startSec:18, endSec:48, temperature:-0.12, saturation:0.92, contrast:1.10 } apply_global_color_grade { startSec:48, endSec:72, temperature:0.10, saturation:1.18, contrast:1.18 } set_layer_lut { layerId:"video6", id:"teal-orange", intensity:0.55 } # Vignette to focus the eye on the performer set_layer_video_effects { layerId:"video6", effects:[{ effectId:"vignette", intensity:35 }] } # Filmstrip + parity check catch a mask/beat mismatch before the final render preview_filmstrip { fromSec:44, toSec:56, frames:6 } capture_canvas { timeSec:48.75, outlinesOnly:true } verify_export_parity { timesSec:[6,48.75,96] } set_export_settings { resolution:"1080x1920", fps:30 } export_project {}
"Build a 90-second how-to with inspectable steps, a countdown widget, and safe captions."
Use a widget for the live countdown, keyframe the step badge, and let the agent describe and capture the canvas at each chapter. The verification pass checks text bounds, timing, and the final vertical export.
# Intake — fast for a tutorial Q1: Steps? → 6 steps, ~12-15s each Q2: Voice? → No VO — text-on-screen with ambient kitchen sounds # Plan create_project { name:"Pour-Over 90s", aspectRatio:"9:16", fps:30 } add_video_layer × 6 # video0..video5 — one shot per step add_audio_layer { uri:"kitchen_amb.wav", duration:90, volume:0.5 } add_stock_music_layer { soundId: 6190, duration:90, volume:0.35 } # Step counter — animated number badge, top-right safe for i in 1..6: t0 = (i-1) * 15 # every 15s add_text_layer { text: i+"/6", startTime:t0, duration:14.5, fontSize:42, x:80, y:8, fullWidth:false, fontWeight:"800" } set_layer_border { layerId:"text"+i, width:3, color:"#ffffff", cornerRadius:24 } set_text_animation { layerId:"text"+i, inId:"in-scale-up", inDurationSec:0.3, outId:"out-fade", outDurationSec:0.25 } # Instruction kinetic text — pop on each step start, typewriter reveal instructions = ["RINSE THE FILTER", "ADD 18g COFFEE", "BLOOM 30s · 50g WATER", "POUR TO 150g", "POUR TO 300g", "WAIT · DRINK · ENJOY"] for i, line in enumerate(instructions): t0 = i * 15 add_text_layer { text:line, startTime:t0+0.3, duration:3.5, fontSize:56, verticalAnchor:"top", y:18, fontWeight:"700" } set_typewriter { layerId:"text"+(i+7), charDelayMs:42 } set_text_animation { layerId:"text"+(i+7), outId:"out-slide-up", outDurationSec:0.4 } # Countdown widget for the 30s bloom step (starts at 32s) add_widget_layer { widgetId:"countdown", startTime:32.0, duration:30, config:{ countdownFromMs:30000, fontSize:120, textColor:"#fbbf24", fontWeight:"bold" } } # Clean daylight grade — bright, accurate, slight warmth (multipliers around 1.0 = neutral) apply_global_color_grade { brightness:1.10, contrast:1.05, saturation:1.10, temperature:-0.05 } # Light music swell on the last step set_layer_volume_keyframes { layerId:"audio1", keyframes:[ {timeMs:0, volume:0.35}, {timeMs:75000,volume:0.55}, {timeMs:90000,volume:0.0} ]} # Inspect each chapter before export — bounds and timing are part of the plan describe_canvas { timeSec:15 } capture_canvas { timeSec:45, grid:true } preview_filmstrip { fromSec:0, toSec:90, frames:6 } verify_export_parity { timesSec:[15,45,75] } set_export_settings { resolution:"1080x1920", fps:30 } export_project {}
"Shape a 75-second wedding highlight with clean vows, gentle motion, and a keepsake template."
Isolate and boost the vows, auto-duck the music, apply a warm look, and use a soft heart or ellipse mask for the rings detail. Save a full-fidelity snapshot locally; only submit it to Community after explicit approval.
# Intake Q1: Aspect & length? → 16:9 1080p 24fps (cinematic), 75s Q2: Music? → "Something soft, piano, no lyrics" Q3: VO? → Use a 25-second slice of the bride's vows, recorded on a phone mic Q4: Names & date? → "Sara & Adam · June 14, 2026" # Structure (emotional arc, not beats) 00:00–00:12 Morning prep — soft details, dress, rings 00:12–00:30 Ceremony walk + kiss (slow-mo on the kiss) 00:30–00:55 Vows VO over wide reception shots 00:55–01:15 Dance + party + tail card # Plan create_project { name:"Sara & Adam", aspectRatio:"16:9", fps:24 } add_video_layer × 18 # curated highlights video0..video17 add_audio_layer { uri:"bride_vows.wav", startTime:30, duration:25 } add_stock_music_layer{ soundId: 9021, startTime:0, duration:75, volume:0.45 } # Dialogue boost — separate the phone-mic vows, raise vocals, duck room noise isolate_voice { layerId:"audio0", boostDb:7, tier:"studio" } # Sparkle Lottie on the rings detail (around 6s) add_lottie_layer { presetId:"sparkles", startTime:5.5, duration:2.5, x:50, y:50 } # Speed-ramp into the kiss set_layer_speed { layerId:"video5", speed:0.5, keepPitch:true } keyframe_add { layerId:"video5", property:"transform.scale", timeMs:0, value:1.0 } keyframe_add { layerId:"video5", property:"transform.scale", timeMs:1200, value:1.12, interp:{type:"preset",name:"easeInOut"} } keyframe_add { layerId:"video5", property:"transform.scale", timeMs:2400, value:1.06 } # Cross-fade transitions between clips in the slow sections set_layer_transition { layerId:"video1", out:{ id:"dissolve", durationSec:0.6 } } set_layer_transition { layerId:"video2", out:{ id:"dissolve", durationSec:0.6 } } # Custom warm-cream LUT (round-trip from a reference still) bake_cdl_to_cube { cdl:{ slope:[1.04,1.02,0.95], offset:[0.02,0.01,-0.01], power:[1.0,1.0,1.04], saturation:0.96 }, size:33 } lut_register_custom { id:"sara-adam-warm", name:"Sara & Adam Warm", cubeText:"<cube text>" } apply_global_color_grade { lutId:"sara-adam-warm", lutIntensity:0.72, brightness:1.10, saturation:1.05, temperature:0.04 } # Auto-duck — music drops automatically wherever the isolated vocal stem is active auto_duck { musicLayerId:"audio1", vocalSourceLayerId:"audio0", duckDb:-14 } # Tail card — names and date with elegant fade add_text_layer { text:"Sara & Adam", startTime:68.0, duration:7.0, fontSize:90, verticalAnchor:"center", fontFamily:"Serif Display", fontWeight:"400" } add_text_layer { text:"June 14, 2026", startTime:68.0, duration:7.0, fontSize:36, y:56, fontFamily:"Serif Display" } set_text_animation { layerId:"text0", inId:"in-fade-up", inDurationSec:1.0 } set_text_animation { layerId:"text1", inId:"in-fade-up", inDurationSec:1.0 } keyframe_add { layerId:"text0", property:"opacity", timeMs:6000, value:1.0 } keyframe_add { layerId:"text0", property:"opacity", timeMs:7000, value:0.0, interp:{type:"preset",name:"easeIn"} } # Rings detail — a soft ellipse sample with a stored easing curve create_mask_animation { layerId:"video2", shape:"ellipse", pattern:"ellipse:sample:drift", curve:"easeInOut" } preview_filmstrip { fromSec:4.5, toSec:8, frames:4 } verify_export_parity { timesSec:[6,35,70] } set_export_settings { resolution:"1920x1080", fps:24, quality:"high" } export_project {}
"Cut a 45-second sports hype reel with drum-locked edits and a mask punch on the drop."
Use onset detection for the cut rhythm, then combine a glitch burst with a mask animation using bounce or elastic easing. Inspect the score card in x-ray mode, confirm the curve has two keyframes, and verify the rendered parity.
# Intake Q1: Platform? → IG Reel + TikTok cross-post, 9:16 1080p 30fps Q2: Energy? → "Maximum. Like a hype train hit a building." Q3: Music? → trap-style instrumental, no vocals create_project { name:"Wildcats Playoff", aspectRatio:"9:16", fps:30 } add_video_layer × 16 # video0..video15 — dunks, blocks, threes, crowd add_stock_music_layer { soundId: 7734, startTime:0, duration:45 } # Real onset detection on the drum stem — punchier than fixed BPM math separate_audio { layerId:"audio0", tier:"fast" } beat_cut_from_drums { sourceLayerId:"audio0", sensitivity:0.8, maxCuts:30 } # → cutTimesMs drives every split below (atSec relative to each clip's own startTime) split_layer { layerId:"video0", atSec:0.43 } split_layer { layerId:"video1", atSec:0.43 } # ...repeat split_layer per returned cut through the verse and the double-time chorus # Drop hits — glitch burst + zoom punch on the dunk clip set_layer_effect { layerId:"video6", effectId:"glitch", params:{ amount:0.6 } } keyframe_add { layerId:"video6", property:"fx.intensity", timeMs:0, value:0.6 } keyframe_add { layerId:"video6", property:"fx.intensity", timeMs:200, value:0.0 } keyframe_add { layerId:"video6", property:"transform.scale", timeMs:0, value:1.0, interp:{type:"preset",name:"easeIn"} } keyframe_add { layerId:"video6", property:"transform.scale", timeMs:120, value:1.25 } keyframe_add { layerId:"video6", property:"transform.scale", timeMs:430, value:1.0 } set_layer_transition { layerId:"video6", in:{ id:"flash", durationSec:0.08 } } # Chorus whip-pans on the punchiest hits set_layer_transition { layerId:"video8", out:{ id:"wipeLeft", durationSec:0.12 } } set_layer_transition { layerId:"video11", out:{ id:"zoomBlur", durationSec:0.14 } } # Big text — "WILDCATS" punch on drop add_text_layer { text:"WILDCATS", startTime:6.7, duration:1.4, fontSize:180, verticalAnchor:"center", fontWeight:"900" } set_layer_border_glow{ layerId:"text0", color:"#ef4444", intensity:85, radius:24, mode:"pulse" } keyframe_add { layerId:"text0", property:"transform.scale", timeMs:0, value:0.0 } keyframe_add { layerId:"text0", property:"transform.scale", timeMs:120, value:1.3, interp:{type:"preset",name:"easeOut"} } keyframe_add { layerId:"text0", property:"transform.scale", timeMs:240, value:1.0 } keyframe_add { layerId:"text0", property:"transform.rotation", timeMs:0, value:-5 } keyframe_add { layerId:"text0", property:"transform.rotation", timeMs:240, value:0 } # Moody high-contrast grade apply_global_color_grade { contrast:1.30, saturation:0.90, temperature:-0.15, brightness:0.95 } set_layer_lut { layerId:"video6", id:"bleach-bypass", intensity:0.55 } # Sponsor lower-third (last 5s) add_lower_third_layer { presetId:"sport-aggressive", lines:["@ HighSchool Gym", "Fri 7pm"], startTime:40, duration:5 } # Final score card — one-tap neon shape style instead of hand-tuned glow values add_shape_layer { shape:"rounded_rect", fillColor:"#000000", startTime:42, duration:3, canvasRelativeWidth:70, canvasRelativeHeight:22, x:15, y:39, cornerRadius:18 } set_shape_style { layerId:"shape0", styleId:"neon-pink" } add_text_layer { text:"WILDCATS 78 — TIGERS 64", startTime:42, duration:3, fontSize:50, verticalAnchor:"center", fontWeight:"900" } # Mask punch — Mirror H sample with bounce easing on the drop create_mask_animation { layerId:"video6", shape:"mirror", pattern:"mirror:sample:pulse", curve:"bounce", mirrorAxis:"horizontal" } capture_canvas { timeSec:7.0, xray:true } preview_filmstrip { fromSec:6.5, toSec:8.5, frames:5 } verify_export_parity { timesSec:[7,24,42] } set_export_settings { resolution:"1080x1920", fps:30 } export_project {}
"Create a 60-second property walkthrough with room-safe reveals and a shareable listing template."
Use a diagonal or mirror mask per room, soft edge fades for the footage, animated labels, and a measured warm grade. Check every reveal with a filmstrip, export the listing, then offer a sanitized Community template with its thumbnail and demo.
# Intake Q1: Aspect? → 16:9 1080p 30fps for MLS + Instagram Q2: Tone? → "Quiet luxury — let the house speak" Q3: CTA? → Agent name + phone number + open-house date # Structure — 7 rooms × ~8s each + 4s opening + 4s closing 00:00–00:04 Aerial drone — front of house with title overlay 00:04–00:12 Entry / great room 00:12–00:20 Kitchen 00:20–00:28 Primary suite 00:28–00:36 Pool deck 00:36–00:44 Wine cellar 00:44–00:52 Sunset terrace 00:52–01:00 Price card + agent CTA # Plan create_project { name:"Malibu Listing", aspectRatio:"16:9", fps:30 } add_video_layer × 7 # video0..video6, one per room add_stock_music_layer { soundId: 3387, duration:60, volume:0.5 } # Slow push-ins everywhere — the room should feel like it's revealing itself for clipId in [video0..video6]: keyframe_add { layerId:clipId, property:"transform.scale", timeMs:0, value:1.00 } keyframe_add { layerId:clipId, property:"transform.scale", timeMs:7800, value:1.05, interp:{type:"preset",name:"easeInOut"} } # Shader-driven wipe between rooms — clean diagonal sweep on every cut for clipId in [video0..video5]: set_layer_transition { layerId:clipId, out:{ id:"slicewipe", durationSec:0.55 } } # Animated room labels — clean serif, slide-in from left, hold, fade labels = ["GREAT ROOM","KITCHEN","PRIMARY SUITE","POOL DECK","WINE CELLAR","SUNSET TERRACE"] for i, l in enumerate(labels): t0 = 4 + i*8 add_text_layer { text:l, startTime:t0+0.4, duration:6.0, fontSize:48, x:8, y:86, fontFamily:"Serif Display", fontWeight:"300", textAlign:"left", fullWidth:false } keyframe_add { layerId:"text"+i, property:"transform.x", timeMs:0, value:-30 } keyframe_add { layerId:"text"+i, property:"transform.x", timeMs:600, value:8, interp:{type:"preset",name:"easeOut"} } keyframe_add { layerId:"text"+i, property:"opacity", timeMs:5400, value:1.0 } keyframe_add { layerId:"text"+i, property:"opacity", timeMs:6000, value:0.0, interp:{type:"preset",name:"easeIn"} } # Price card — Pattern-fill (gold foil texture) border instead of a flat stroke add_shape_layer { shape:"rounded_rect", fillColor:"#1a1a1a", startTime:52, duration:8, canvasRelativeWidth:60, canvasRelativeHeight:40, x:20, y:30, cornerRadius:16 } set_shape_outline { layerId:"shape0", color:"#d4a056", width:2, align:"outside" } set_shape_glow { layerId:"shape0", color:"#d4a056", mode:"breathe", intensity:40, radius:10, placement:"outer" } add_text_layer { text:"$12,450,000", startTime:52.5, duration:7.5, fontSize:72, verticalAnchor:"center", y:42, fontFamily:"Serif Display", fontWeight:"300" } add_text_layer { text:"5 BD · 7 BA · 8,400 SF", startTime:52.5, duration:7.5, fontSize:30, y:52, fontWeight:"400" } add_lower_third_layer { presetId:"elegant-thin", lines:["Maya Linden · Coldwell Banker", "(310) 555-0182 · Open Sat 1–4pm"], startTime:54, duration:6 } # Soft warm grade (multipliers around 1.0 = neutral) apply_global_color_grade { brightness:1.10, contrast:1.08, saturation:1.08, temperature:0.08 } # Opening title — mask-wipe reveal add_text_layer { text:"22820 Pacific Coast Highway", startTime:0.5, duration:3.5, fontSize:52, verticalAnchor:"center", fontFamily:"Serif Display", fontWeight:"300" } create_mask_animation { layerId:"text6", shape:"rectangle", pattern:"rectangle:sample:reveal", curve:"easeOut" } # Room reveal review + community handoff (private media stripped by default) set_layer_fade_mask { layerId:"video0", mode:"inset", softness:0.24 } preview_filmstrip { fromSec:0, toSec:60, frames:8 } verify_export_parity { timesSec:[1,28,56] } save_project_as_template { id:"malibu-listing", name:"Malibu Listing", mode:"snapshot" } set_export_settings { resolution:"1920x1080", fps:30 } export_project {} # use the returned MP4 path for the Community demo submit_template { id:"malibu-listing", name:"Malibu Listing", category:"real-estate", stripPrivateMedia:true, thumbnailUri:"file:///tmp/malibu-thumb.jpg", previewVideoUri:"file:///tmp/malibu-demo.mp4" }
Each plan is a complete tool composition — an agent could send these calls to a connected ExpoCut MCP server and the listed render would appear on the device. Mix and match: a wedding cut can borrow the music-video lyric overlay; a tutorial can borrow the brand-spot end-card reveal. Tip: use ← / → arrow keys to flip between examples.
Conversation starters for assistants.
"Help me cut a Reel"
A guided flow: pick clips, choose a vibe, set duration, auto-suggest transitions and music.
"Make this look cinematic"
Recommend a LUT, set 2.39:1 letterbox, apply teal/orange grade, slow the timeline 12%.
"Add captions"
Suggest a caption style, font, position, and timing for the spoken audio in the clip.
"Trim to 30 seconds"
Identify the strongest beats and propose cuts that fit the target duration.
Run commands from your browser.
Connect to the MCP server running on your phone and execute tool calls directly — no CLI needed. Paste the address and token from ExpoCut → Settings → AI Integration.
Questions, answered.
The essentials about the connector, privacy, and how agents stay current.
Does ExpoCut have a Model Context Protocol (MCP) server?
What can an AI agent actually do with it?
describe_canvas, capture_canvas, preview_filmstrip); and export up to 4K — every step undoable.How does an AI assistant connect to ExpoCut?
How do agents discover the current capabilities?
list_effects, list_shaders, list_transitions, list_shapes, list_fonts, get_effect_schema, get_layer_schema — for the live catalogs.Is it free, and where does the work run?
Live, on the device.
ExpoCut ships an in-app MCP server. When the user enables it, an MCP-compatible client like Claude Desktop connects to a local port on the device and can call the tools above. The server is off by default, accepts requests only from private or loopback network addresses, and every call is recorded as an undoable command in the on-device edit-history log. This page is the public reference for that surface so agents can index it and reason about the app.
Build with ExpoCut.
Download the app, or read the matching Skill for AI assistants.
Join the beta View Skill →