Connect via CLI

Open ExpoCut → Settings → enable the connector. Copy the address and token it shows, then run one of these in your terminal:

claude mcp add --transport http expocut \
  http://<device-ip>:<port>/mcp \
  --header "Authorization: Bearer <token>"
The address and token are shown in ExpoCut → Settings once the connector is on. Replace <device-ip>, <port>, and <token> with those values.
Model Context Protocol · 225 tools · on-device

Drive ExpoCut with your AI agent.

A Model Context Protocol reference for ExpoCut's editing surface — 225 typed tools an AI assistant can call to plan, draft, and build videos with you, right on your phone.

225TYPED TOOLS
20FAMILIES
On-deviceNO CLOUD EDIT
Off by defaultLOCAL INTERFACE
Overview

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.

How it works

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.

01 · Constellation
Every desk has an agent. Every pocket has ExpoCut.

Connect from any MCP-compatible client. The connector lives inside the App, binds to a local interface, and exposes a typed tool surface the agent can call. Seven popular clients shown — bring your own.

MCP · LOCAL PREVIEW ExpoCut Claude ChatGPT Cursor Continue { } Cline Windsurf Z Zed App runs on your device
02 · Sequence
One sentence in. A render out.

A natural-language request flows through the agent, into the connector, through the editor's command bus, and back as an undoable, auditable result.

You creator "Trim my Bali clips to 30 seconds, add captions" AI Agent plans the edit Connector tool surface · local Editor runs the commands Result undoable · auditable prompt tool calls commands render
03 · Toolbelt
Eight families. One engine.

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.

EXPOCUT Engine Project create · export Media import · stock Timeline addLayer · trim Effect apply · transition Export render · share Audio mix · duck AI captions · cutout Color grade · LUT
Quickstart

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.

1

Enable the connector

In ExpoCut → Settings, switch on the in-app MCP server. It is off by default and binds only to a local interface on the device.

2

Connect your AI client

Point any MCP-compatible client at the local address ExpoCut shows. For 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.

3

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
Concepts

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 secondsAll 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 firstLayer & setter tools need an open project — call create_project then open_project (it auto-navigates to the editor).
Editor must be mountedcapture_canvas, preview_filmstrip and export_project drive the live editor; open_project makes this just work.
Catalog before setterTools that take an id reject unknown ids with a hint pointing back to the matching list_* / get_effect_schema.
Z-order: 0 is frontNew layers land at trackIndex 0 (front); everything else shifts back. Use reorder_layer to restack.
Discover at runtimeCatalogs evolve — call list_effects, list_shaders, list_transitions, list_fonts, list_shapes… for the live set.
Every call is undoableEach tool call is recorded as a reversible step in the on-device edit history.
Tools

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.

create_project

Start a new project

Create a new edit at a target resolution and aspect ratio. Templates available for Reels, TikTok, YouTube, and Shorts.

namestring
aspectRatio"9:16" | "16:9" | "1:1" | "4:5"
resolution"1080p" | "2K" | "4K"
frameRate24 | 30 | 60
add_video_layer

Bring in clips

Import video, audio, or image media from the device gallery, Pexels stock, Freesound audio, or a remote URL.

source"gallery" | "pexels" | "freesound" | "url"
type"video" | "image" | "audio"
querystring (for stock sources)
add_text_layer

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.

layerType"video" | "audio" | "image" | "text" | "lowerThird" | "lottie" | "shape"
startseconds
durationseconds
trim_layer

Trim & split

Adjust in/out points or split a clip at the playhead. Ripple edits keep the rest of the timeline aligned.

clipIdstring
inseconds
outseconds
rippleboolean
set_layer_effect

Apply visual effects

Choose from 130+ effects and 120+ preset filters — color grading, blur, glitch, chroma key, vignette, retro, cinematic LUTs. See them live →

clipIdstring
effectstring (effect id)
intensity0.0 – 1.0
set_layer_audio_transitions

Add transitions

Place one of 49 transitions between adjacent clips: fade, dip-to-black, slide, push, zoom, glitch, whip pan, shader wipes. See them live →

edge{ leftClipId, rightClipId }
stylestring
durationseconds (0.2 – 2.0)
add_lower_third_layer

Titles & captions

Add styled text or one of 120+ animated lower thirds with custom font, color, position, and entrance.

preset"title" | "caption" | "lowerThird:"
contentstring
position{ x, y } (0 – 1)
set_layer_audio_effects

Mix & sweeten audio

Adjust volume, ducking, fade in/out, and apply 47 audio effects across 35 audio transitions.

trackIdstring
volume0.0 – 1.5
duckboolean
effectstring (effect id)
apply_global_color_grade

Color grade

Exposure, contrast, saturation, temperature, tint, shadows, highlights — applied per-clip or globally.

scope"clip" | "global"
params{ exposure, contrast, saturation, temp, tint, shadows, highlights }
export_project

Render & export

Encode the final timeline using the on-device hardware encoder. Saves to camera roll or shares via system sheet.

resolution"720p" | "1080p" | "2K" | "4K"
codec"h264" | "hevc"
bitratenumber (Mbps)
capture_canvas

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.

timeSecnumber (optional, defaults to playhead)
maxWidthnumber (64–1024, default 512)
format"png" | "jpg"
xrayboolean · dim + labeled boxes
outlinesOnlyboolean · borders only
gridboolean · 10% coordinate grid
analyze_pixels

Read pixels at a point

Sample the rendered canvas — useful for AI vision tools that need to check what's actually on-screen.

region{ x, y, w, h } (0 – 1 normalized)
timeseconds
tts_validate_script

Validate a TTS script

Run a script through the TTS pre-flight: catches unsupported characters, unpronounceable tokens, and estimated duration.

scriptstring
voicestring (voice id)
get_effect_schema

Discover effect parameters

Look up the typed parameter schema for any effect, filter, or transition so an assistant can compose valid calls.

idstring (effect id)
set_layer_background_remover

Remove background

On-device segmentation. Produces an alpha mask on a clip or image without uploading frames.

layerIdstring
feather0.0 – 1.0
add_caption_layer_from_audio

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 →

trackIdstring
stylePresetstring
transcribe_audio

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.

layerIdstring
language"auto" | locale
reorder_layer

Reorder / hide layers

Change z-order, bring-to-front, send-to-back, or temporarily hide a layer for A/B compare.

layerIdstring
op"front" | "back" | "up" | "down" | "hide" | "show"
add_widget_layer

Add a dynamic widget

Place a countdown timer or stopwatch as a first-class timeline layer with editable target time and styling.

kind"countdown" | "stopwatch"
startseconds
durationseconds
style{ font, color, size }
import_foreign_file

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.

payloadbase64 | { uri }
hintformat hint (optional)
list_shapes · add_shape_layer

Add a shape

Browse the full library of 137 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.

shapeshape preset id (137 options)
fillColorhex
gradientColors[hex, …]
set_layer_fade_mask

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.

layerIdstring
mode"linear" | "radial" | "inset"
softness0.0 – 1.0
list_light_leaks · add_light_leak_overlay

Light leak overlay

Browse the CDN 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 →

presetIdstring (from list_light_leaks)
intensity0.0 – 1.0
durationseconds
Recently shipped

19 new commands for import, codec, color & audio.

The connector grew to expose the importer pipeline, codec compatibility checks, loudness measurement, CDL/LUT writers, font fallback, and Lottie/FCPXML out-export. Each is callable from any MCP-compatible client.

import_foreign_file · import_from_file_system · detect_format · migrate_template

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.

query_codec_support · default_device_class · resolve_codec_fallback

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.

measure_loudness · gain_to_loudness_target · list_loudness_targets · build_loudness_plan

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.

parse_cube_lut · write_cdl · bake_cdl_to_cube

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.

resolve_font_fallback · audit_font_coverage · list_font_aliases

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.

export_template_to_lottie · export_template_to_fcpxml

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.

Just shipped

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.

keyframe_add

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.

layerIdstring
propertyPropertyPath (40 options)
timeMsnumber (≥ 0)
valuenumber
interp{ type, … } optional
keyframe_add_discrete

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).

propertyDiscretePropertyPath (12 options)
timeMsnumber
valuestring | number | boolean
keyframe_remove_at

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.

layerIdstring
timeMsnumber
keyframe_clear

Reset animation

Clear all keyframes on a layer, or just one property. The "Reset" button as a tool call.

layerIdstring
propertyPropertyPath (optional)
keyframe_list

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.

layerIdstring
keyframe_set_interp

Refine an easing curve

Replace the easing leaving an existing keyframe without touching the value. Use to retune motion without re-adding keyframes.

propertyPropertyPath
timeMsnumber
interp{ type: "hold" | "linear" | "bezier" | "preset", … }
keyframe_snapshot_at

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.

layerIdstring
timeMsnumber
keyframe_set_animation

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.

layerIdstring
animation{ tracks, positionTrack?, discreteTracks? }
list_keyframe_properties

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.

layerType"image" | "text" | "shape" | "video" | "audio" (optional)

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

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.

Just shipped

Let agents invent new effects, LUTs, borders & masks.

When no built-in preset matches the brief, the agent submits a JSON spec and a brand-new asset is admitted to the registry — gated behind a permission setting (Off · Safe · Standard · Full), a static validator, an automated shader probe-pass, per-session quota, and an on-device audit log. All 14 authoring commands are live; flip the tier in Settings → "AI may author assets" to enable.

fx_register_spec

Author a new FX / filter / transition

Submit a full FxSpec — declarative timeline or shader fragment. Validates shape, checks id format, runs the shader probe, then admits to the registry. The id can immediately be used by set_layer_effect.

specFxSpec v1.0
persistboolean (default false)
overwriteIfExistsboolean (default false)
idempotencyKeyuuid (optional)
fx_compose_from_template

Build from a curated family

Lower-risk path: pick a template (e.g. transition.distortion.twirl) and override its variant params. Server materializes the spec — agent never writes raw shader code.

templateIdstring
namestring ≤ 64
params{ [key]: number }
durationMs100–8000
fx_dry_run

Validate without persisting

Validate + sample-evaluate the declarative timeline at t ∈ {0, 0.25, 0.5, 0.75, 1}. Returns the per-track output. Use as the first call when authoring — never writes to the registry.

specFxSpec v1.0
sampleTimesnumber[] (≤ 33)
fx_list_custom · fx_delete_custom

Audit + clean up

List or remove AI-authored specs. Built-in specs are immutable — delete refuses with PermissionDenied. Pass alsoFromDisk: true to delete the persisted file.

author"ai" | "user" | "any"
idstring
alsoFromDiskboolean
lut_register_custom · lut_list_custom · lut_delete_custom

Author a custom LUT

Submit a .cube text payload or a declarative grade descriptor (white balance + tone curve + lift/gamma/gain). LUT_3D_SIZE capped at 33 for AI submissions.

cubeTextstring (≤ 1 MB) — variant A
grade{ whiteBalance, toneCurve, lift } — variant B
border_register_preset · border_list_presets · border_delete_preset

Save a named border preset

Persist a full BorderConfig under an id so the agent can reuse the same look across layers. set_layer_border learns a new presetId argument.

id^(ai|user)\.[a-z0-9-]{1,48}$
configBorderConfig
mask_register_shape · mask_list_custom · mask_delete_custom

Add a new mask shape

Accepts rect, ellipse, polygon (≤ 256 vertices), or path (limited SVG d — M / L / Q / C / Z only). JS bakes geometry to a coverage bitmap; no new native opcodes needed.

geometry{ kind, …shape-specific }
feathernumber px @ 1080p
invertboolean

Permission tiers

A single App setting controls which authoring paths are available. The default is Off. Shader admission is opt-in via the Full tier and a per-session in-app confirmation.

Tier FX declarative FX shader LUT .cube LUT grade Border Mask
Off (default)
Safe rect · ellipse
Standard all geometry
Full ✓ + confirm all geometry

Guard ladder — five layers

Every registration request passes through all five before the asset is admitted. Failure at any layer aborts the request and writes an audit-log entry.

01 · STATIC
Schema validator

Allow-listed kinds & categories. Shader ≤ 32 KB. Expr ≤ 1024 chars. Polygon ≤ 256 vertices. Path commands restricted to M / L / Q / C / Z.

02 · PROBE
Runtime sanity

Shader compiles on a 64×64 off-screen surface with a 250 ms timeout. LUT sample-tested for NaN. Mask rasterized to verify non-empty coverage.

03 · QUOTA
Resource caps

50 registrations per session. 256 FX · 64 LUT · 128 border · 32 mask per install. 10 calls/minute rate limit. LRU eviction when caps hit.

04 · TIER
Permission gate

App setting (Off / Safe / Standard / Full) decides which surfaces the call may touch. persist: true always requires Standard or Full + a user confirmation.

05 · AUDIT
Reversibility

Every admit / reject / delete logged to the on-device audit log. AI Operations screen exposes Disable + Remove for any AI-authored asset.

Failure modes the agent will see

Every tool returns { ok: false, error, message } with one of these stable codes — never a bare exception. Build retries against the code, not the message.

Disabled InvalidSpec InvalidId AlreadyExists NotFound EvalFailed ProbeFailed PersistFailed DeleteFailed PermissionDenied OverQuota RateLimited NotImplemented

Known limitations

Example — register a declarative transition

Lightweight Y-axis whip-pan transition, authored entirely as keyframes — no shader code.

{
  "FXVSN": "1.0",
  "id": "ai.whip-pan-y-soft",
  "name": "Whip Pan Y · soft",
  "author": "ai",
  "kind": "transition",
  "category": "Transition",
  "renderer": "declarative",
  "timeline": {
    "duration": "auto",
    "tracks": [
      { "param": "translateY",
        "keyframes": [[0, 0], [0.4, -120], [0.6, 120], [1, 0]],
        "easing": "easeInOutCubic" },
      { "param": "blur",
        "keyframes": [[0, 0], [0.5, 8], [1, 0]] }
    ]
  }
}

Dry-run first via fx_dry_run, then register with fx_register_spec { spec, persist: false }, then apply via set_layer_effect { layerId, effectId: "ai.whip-pan-y-soft" }. Each authoring surface is admitted through a typed validation + guard pass before it can touch a project.

Full surface

All 225 tools, by family.

Every command the connector registers. Names are the canonical tool IDs — copy-paste into your agent's tool catalog. Green chips are recently shipped.

Projects

8 TOOLS
list_projects create_project open_project get_active_project save_project rename_project duplicate_project delete_project

Layers · basic

6 TOOLS
list_layers add_text_layer add_shape_layer add_layout update_layer remove_layer

Media layers

3 TOOLS
add_video_layer add_image_layer add_audio_layer

Stock & music

9 TOOLS
stock_search_photos stock_search_videos stock_curated_photos stock_popular_videos add_stock_image_layer add_stock_video_layer stock_search_music stock_trending_music add_stock_music_layer

Cuts & order

4 TOOLS
trim_layer split_layer reorder_layer set_layer_visibility

Effects & polish

20 TOOLS
set_layer_effect set_layer_filter set_layer_video_effects set_layer_audio_effects set_layer_audio_transitions set_layer_audio_offset set_layer_border set_layer_border_glow set_shape_glow set_shape set_shape_fill set_shape_outline set_shape_style set_layer_color_adjust set_layer_fade set_layer_crop set_layer_chroma_key set_layer_lut set_layer_volume_keyframes set_layer_overlay_color

Masks & procedural

9 TOOLS
set_layer_mask set_layer_background_remover add_generative_bg_layer add_procedural_filter_layer set_layer_fade_mask clear_layer_fade_mask add_light_leak_overlay set_shader_source clear_shader_source

Color

1 TOOL
apply_global_color_grade

Text & animation

8 TOOLS
add_lottie_layer add_lower_third_layer set_text_animation set_typewriter set_layer_scale_xy list_lower_thirds list_text_animations add_widget_layer

TTS & Speech-to-Text

5 TOOLS
tts_add_audio_layer tts_validate_script tts_list_voices transcribe_audio add_caption_layer_from_audio

Catalogs

18 TOOLS
list_effects get_effect_schema list_transitions list_widgets list_video_effects list_audio_effects list_audio_transitions list_shapes list_light_leaks list_fonts list_text_styles list_text_effects list_shaders list_layouts list_shape_styles list_shape_textures list_mesh_gradients list_border_presets

Canvas, preview & render

10 TOOLS
get_canvas_info describe_canvas capture_canvas preview_filmstrip render_still get_render_status analyze_pixels set_export_settings export_project seek

Importer · NEW

5 TOOLS
import_foreign_file import_from_file_system detect_format migrate_template parse_cube_lut

Codec · NEW

3 TOOLS
query_codec_support default_device_class resolve_codec_fallback

Loudness · NEW

4 TOOLS
measure_loudness gain_to_loudness_target list_loudness_targets build_loudness_plan

Color writers · NEW

2 TOOLS
write_cdl bake_cdl_to_cube

Cross-format export · NEW

2 TOOLS
export_template_to_lottie export_template_to_fcpxml

Font fallback · NEW

3 TOOLS
resolve_font_fallback audit_font_coverage list_font_aliases

FX Authoring · NEW

5 TOOLS
fx_register_spec fx_compose_from_template fx_dry_run fx_list_custom fx_delete_custom

LUT Authoring · NEW

3 TOOLS
lut_register_custom lut_list_custom lut_delete_custom

Border Authoring · NEW

3 TOOLS
border_register_preset border_list_presets border_delete_preset

Mask Authoring · NEW

3 TOOLS
mask_register_shape mask_list_custom mask_delete_custom

Keyframes · NEW

9 TOOLS
keyframe_add keyframe_add_discrete keyframe_remove_at keyframe_clear keyframe_list keyframe_set_interp keyframe_snapshot_at keyframe_set_animation list_keyframe_properties

Motion path & text-2 · NEW

17 TOOLS
set_layer_motion_path clear_motion_path list_motion_path_presets update_text set_text_font set_text_style set_text_effect set_text_style_runs clear_text_style_runs set_text_shade set_text_wrap_box set_text_writing_direction set_text_path clear_text_path set_text_animation_range set_text_font_variations set_text_font_features

Audio companion & layout · NEW

9 TOOLS
mute_video_audio unlink_video_audio relink_video_audio set_stretch_pan set_layer_fit_mode set_layer_anchor set_aspect_lock set_blur_fill set_shape_canvas_relative_size

Advanced compositing · NEW

11 TOOLS
set_layer_parent set_layer_is_null set_track_matte clear_track_matte set_layer_is_adjustment set_time_remap clear_time_remap set_layer_blend_mode set_layer_cdl set_working_color_space set_layer_alpha_mode

Widget config · NEW

3 TOOLS
update_widget_config get_widget_config bar_chart_race.set_data

Templates & Reels · NEW

8 TOOLS
list_templates apply_template import_template_json save_project_as_template set_template_theme list_reels apply_reel swap_reel_theme

Brand profiles · NEW

6 TOOLS
list_brand_profiles get_brand_profile create_brand_profile update_brand_profile delete_brand_profile apply_brand_profile_to_project

Session control · NEW

16 TOOLS
select_layer get_selection play pause playback_status list_tracks set_track_visibility set_track_mute set_track_volume reorder_track save_history_checkpoint undo redo undo_to_checkpoint list_checkpoints seek_seconds

Introspection & verify · NEW

4 TOOLS
get_layer get_layer_schema get_project verify_export_parity

Builtins

2 TOOLS
ping echo
No tools match that term. Try a feature word like shape, caption, glow, or export.

Param schemas live in llms-full.txt. Call get_effect_schema at runtime for the canonical signature of any effect.

Creative recipes

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.

After Effects · Scale Pop

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.

keyframe_add { layerId, property: "transform.scale", timeMs: 0,    value: 0.0, interp: { type: "easeOut" } }
keyframe_add { layerId, property: "transform.scale", timeMs: 420,  value: 1.08, interp: { type: "easeIn" } }
keyframe_add { layerId, property: "transform.scale", timeMs: 560,  value: 1.0 }
After Effects · Position Travel

Shape slides across with motion blur fake

Move a shape left → right while ramping opacity for a streak feel. Pair with fx.blur keyframes for true smear.

keyframe_add { layerId, property: "transform.x", timeMs: 0,    value: -0.4 }
keyframe_add { layerId, property: "transform.x", timeMs: 600,  value: 0.5, interp: { type: "easeInOut" } }
keyframe_add { layerId, property: "fx.blur",     timeMs: 0,    value: 0.6 }
keyframe_add { layerId, property: "fx.blur",     timeMs: 600,  value: 0.0 }
After Effects · Spin Loop

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 }
After Effects · Mask Wipe

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: "rect", rect: { x: 0, y: 0, width: 0, height: 1 } }
keyframe_add    { layerId, property: "mask.rect.width", timeMs: 0,   value: 0 }
keyframe_add    { layerId, property: "mask.rect.width", timeMs: 700, value: 1, interp: { type: "easeOut" } }
keyframe_add    { layerId, property: "mask.feather",    timeMs: 0,   value: 0.04 }
CapCut · Beat Sync

Cut on every beat of an 118-BPM track

Compute beat times (60000/BPM ms), then split each layer at those marks. Pair with whip-pan transitions on the punchiest hits.

# 118 BPM ⇒ beat every ~508 ms
split_layer  { layerId: "v0", timeMs: 508 }
split_layer  { layerId: "v1", timeMs: 1016 }
split_layer  { layerId: "v2", timeMs: 1524 }
set_layer_audio_transitions {
  edge: { leftClipId: "v1", rightClipId: "v2" },
  style: "whip-pan", duration: 0.18
}
CapCut · Auto Captions

On-device captions with a styled lower-third

Transcribe → place captions → apply a caption-style preset and brand color. Two calls, fully time-aligned.

transcribe_audio            { layerId: "voice", language: "auto" }
add_caption_layer_from_audio {
  trackId: "voice",
  stylePreset: "bold-pop",
  position: { x: 0.5, y: 0.82 }
}
CapCut · Zoom Punch

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: "easeIn" } }
keyframe_add { layerId, property: "transform.scale", timeMs: 1020, value: 1.18 }
keyframe_add { layerId, property: "transform.scale", timeMs: 1480, value: 1.0 }
DaVinci-style · Color

Teal-orange cinematic grade with LUT bake

Write an ASC CDL, bake it to a .cube LUT, register it as a custom LUT, and apply globally — the round-trip a colorist would use.

write_cdl           { slope:[1.05,1.02,0.96], offset:[0.01,0.00,-0.02], power:[1.0,1.0,1.05], sat: 1.12 }
bake_cdl_to_cube    { cdlId: "...", lutSize: 33 }
lut_register_custom { name: "cinematic-teal-orange", cube: "..." }
set_layer_lut       { layerId: "all", lut: "cinematic-teal-orange", intensity: 0.65 }
Agent-authored FX

Compose a custom "whip-pan-y" transition

Author the FX as a typed spec, dry-run it against the device, then register and apply.

fx_dry_run        { spec: { id: "ai.whip-pan-y-soft", ... } }
fx_register_spec  { spec, persist: false }
set_layer_effect  { layerId, effectId: "ai.whip-pan-y-soft", intensity: 0.8 }
CapCut · Bg Replace

Cut subject out, slot onto a generative backdrop

On-device segmentation removes the original background; a generated gradient backdrop sits underneath. Zero uploads.

set_layer_background_remover { layerId: "subject", feather: 0.08 }
add_generative_bg_layer      { preset: "soft-violet-gradient", start: 0, duration: 30 }
reorder_layer                { layerId: "subject", op: "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.

Resources

What an assistant can reference.

Stable URIs an agent can cite when explaining what's available inside ExpoCut.

See them move — live, in-browser previews in the Showcase:

Worked examples

Eight productions. Eight plans.

Each plan below is a full creative direction — intake, structure, tool composition, and export — exactly as a senior assistant should respond. Copy any one into your system prompt to anchor an agent's style. Every call uses the 225-tool MCP surface; nothing here is pseudo-code.

01 / 08 — TRAVEL REEL
01 · Travel Reel · 30s · 9:16

"Make a 30-second Bali Reel with upbeat music and captions."

Beat-synced cut to a 118 BPM track, teal-orange grade, hook in the first 2 seconds, lower-third location stamp, ducked music under VO, captions on bottom-safe.

# 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, around 118 BPM"

# Structure (3-part)
Hook   (0–2.5s):  strongest beach drone push-in.
Middle (2.5–26s): 6 cuts on beat — temple, food, scooter, sunset, waterfall, friend.
Payoff (26–30s): wide drone with location title + soft fade-to-grade.

# Plan — 22 calls, beat math: 60000/118 = ~508 ms/beat
create_project           { name: "Bali Reel", aspectRatio: "9:16", resolution: "1080p", fps: 30 }
add_video_layer × 8        # import 8 gallery clips → v0..v7
stock_search_music        { query: "cinematic pop 118 BPM travel" }
add_stock_music_layer     { trackId: "fs.4421", start: 0, duration: 30, volume: 0.85 }

# Hook — drone push-in + scale punch on first beat (508ms)
trim_layer      { layerId:"v0", in: 0.4, out: 2.9, ripple: false }
keyframe_add    { layerId:"v0", property:"transform.scale", timeMs:0,    value:1.0 }
keyframe_add    { layerId:"v0", property:"transform.scale", timeMs:508,  value:1.06, interp:{type:"easeOut"} }
keyframe_add    { layerId:"v0", property:"transform.scale", timeMs:2500, value:1.0 }

# Middle — 6 beat-aligned cuts with mixed transitions
split_layer   { layerId:"v1", timeMs:2500 } # temple
split_layer   { layerId:"v2", timeMs:6600 } # food (every 4 beats)
split_layer   { layerId:"v3", timeMs:10700 }# scooter
split_layer   { layerId:"v4", timeMs:14800 }# sunset
split_layer   { layerId:"v5", timeMs:18900 }# waterfall
split_layer   { layerId:"v6", timeMs:23000 }# friend smile
set_layer_audio_transitions { edge:{leftClipId:"v2",rightClipId:"v3"}, style:"whip-pan",  duration:0.18 }
set_layer_audio_transitions { edge:{leftClipId:"v4",rightClipId:"v5"}, style:"zoom-blur", duration:0.20 }
set_layer_audio_transitions { edge:{leftClipId:"v5",rightClipId:"v6"}, style:"flash",     duration:0.10 }

# Look — global cinematic grade, then signature LUT
apply_global_color_grade { scope:"global", params:{ exposure:+0.15, contrast:+0.10, saturation:+0.18, temp:-8, shadows:+0.05 } }
set_layer_lut             { layerId:"all", lut:"cinematic-teal-orange", intensity:0.6 }
set_layer_effect          { layerId:"v6", effect:"light-leak", intensity:0.25 }

# Titles & lower thirds
add_text_layer            { content:"BALI", start:0.2, duration:2.2, fontSize:96, position:{x:0.5,y:0.42}, fontWeight:900 }
set_text_animation        { layerId:"t0", inId:"scale-pop", inDurationSec:0.45, outId:"fade-up", outDurationSec:0.35 }
add_lower_third_layer     { preset:"lowerThird:bold-pop", content:"Day 2 — Ubud", start:18.0, duration:3.0, position:{x:0.5,y:0.85} }

# Mix — duck music under any future VO; fade out last beat
set_layer_volume_keyframes { layerId:"music", keyframes:[
    {timeMs:0,    volume:0.85},
    {timeMs:27000,volume:0.85},
    {timeMs:30000,volume:0.0}
]}

# Export
set_export_settings { resolution:"1080p", codec:"h264", fps:30, bitrate:14 }
export_project      {}
02 · Cinematic Brand Spot · 60s · 16:9

"Launch video for a premium coffee brand — feel like a Super Bowl spot."

Slow push-ins, anamorphic letterbox feel, hero-shot title cards with mask reveals, brand-LUT round-trip via CDL bake, ambient bed under VO, dynamic loudness pass.

# Intake
Q1: Platform?          → YouTube pre-roll, 16:9 1080p 24fps (film cadence)
Q2: Brand palette?     → Espresso brown, cream, gold — reference still attached
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 — 28 calls, film-style 24fps
create_project     { name:"Origin — 60", aspectRatio:"16:9", resolution:"1080p", fps:24 }
add_video_layer × 6  # 6 hand-picked hero shots → h0..h5
add_stock_music_layer{ query:"ambient cinematic strings 70 BPM", start:0, duration:60, volume:0.4 }

# Anamorphic feel — black bars + slight horizontal squeeze
add_shape_layer    { kind:"rect", color:"#000000", start:0, duration:60, position:{x:0.5,y:0.0},   size:{w:1.0,h:0.12} }
add_shape_layer    { kind:"rect", color:"#000000", start:0, duration:60, position:{x:0.5,y:1.0},   size:{w:1.0,h:0.12} }

# Slow push-ins on every hero shot (24fps cinematic cadence)
for clipId in [h0..h5]:
  keyframe_add { layerId:clipId, property:"transform.scale", timeMs:0,        value:1.00 }
  keyframe_add { layerId:clipId, property:"transform.scale", timeMs:durationMs, value:1.07, interp:{type:"easeInOut"} }

# Title card 1 — mask wipe reveal of brand name
add_text_layer      { content:"ORIGIN", start:8.0, duration:4.0, fontSize:120, position:{x:0.5,y:0.5}, fontFamily:"Serif Display" }
set_layer_mask      { layerId:"t0", shape:"rect", rect:{x:0,y:0,width:0,height:1}, feather:0.02 }
keyframe_add        { layerId:"t0", property:"mask.rect.width", timeMs:0,    value:0 }
keyframe_add        { layerId:"t0", property:"mask.rect.width", timeMs:900,  value:1, interp:{type:"easeOut"} }
keyframe_add        { layerId:"t0", property:"opacity",         timeMs:3200, value:1 }
keyframe_add        { layerId:"t0", property:"opacity",         timeMs:4000, value:0, interp:{type:"easeIn"} }

# Brand LUT — write CDL from reference still, bake, register, apply
write_cdl             { slope:[1.02,0.98,0.93], offset:[0.02,0.01,-0.02], power:[1.05,1.0,1.08], sat:0.92 }
bake_cdl_to_cube      { cdlId:"cdl_origin_v1", lutSize:33 }
lut_register_custom   { name:"origin-warm", cube:"..." }
set_layer_lut         { layerId:"all", lut:"origin-warm", intensity:0.78 }

# VO + duck music + loudness
tts_validate_script  { script:"Some things take time. Patience. Heat. A hand that knows.", voice:"warm-male-en-us" }
tts_add_audio_layer  { layerId:"vo", start:14.0, voice:"warm-male-en-us" }
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}
]}
measure_loudness     { layerId:"mix" }
build_loudness_plan  { target:"-14 LUFS" }  # streaming-friendly

# Logo reveal end card — scale-pop + glow border
add_image_layer      { uri:"asset://logo.png", start:54.0, duration:6.0, position:{x:0.5,y:0.5}, size:{w:0.4,h:0.4} }
keyframe_add          { layerId:"logo", property:"transform.scale", timeMs:0,   value:0.0, interp:{type:"easeOut"} }
keyframe_add          { layerId:"logo", property:"transform.scale", timeMs:480, value:1.06, interp:{type:"easeIn"} }
keyframe_add          { layerId:"logo", property:"transform.scale", timeMs:620, value:1.0 }
set_layer_border_glow { layerId:"logo", color:"#d4a056", intensity:0.6, radius:0.04 }

# Export — film codec profile
set_export_settings { resolution:"1080p", codec:"h264", profile:"high", fps:24, bitrate:20 }
export_project      {}
03 · Documentary Short · 3:00 · 16:9

"3-minute doc piece about a street vendor in Mumbai."

Interview A-roll with synchronized B-roll cutaways, on-device captions in the speaker's language, lower-third intro, chapter markers via dip-to-black, vintage-film grade.

# Intake
Q1: Aspect & length? → YouTube 16:9 1080p 30fps, 3:00 hard cap
Q2: Subject lang?    → Hindi interview, captions in English
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 synced 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", resolution:"1080p", fps:30 }
add_video_layer          { uri:"interview.mp4", layerId:"a-roll", start:0, duration:180 }
add_audio_layer          { uri:"ambient_market.wav", layerId:"amb", start:0, duration:180, volume:0.25 }
add_stock_music_layer    { query:"ambient indian strings 60 BPM", layerId:"music", start:140, duration:40, volume:0.0 }

# Music swell under cooking montage
set_layer_volume_keyframes { layerId:"music", keyframes:[
    {timeMs:140000,volume:0.0},{timeMs:144000,volume:0.45},
    {timeMs:168000,volume:0.45},{timeMs:172000,volume:0.0}
]}

# B-roll cutaways over the long answers
add_video_layer  { uri:"b-roll-pour.mp4",  layerId:"b0", start:45,  duration:8,  opacity:1.0 }
add_video_layer  { uri:"b-roll-spice.mp4", layerId:"b1", start:72,  duration:7 }
add_video_layer  { uri:"b-roll-hands.mp4", layerId:"b2", start:110, duration:9 }
add_video_layer  { uri:"b-roll-crowd.mp4", layerId:"b3", start:135, duration:5 }
reorder_layer    { layerId:"b0", op:"front" }
# A-roll audio stays under each B-roll cutaway — that's the rule.

# Lower third intro
add_lower_third_layer { preset:"lowerThird:documentary-thin", content:"Ramesh · Vada Pav · Dadar", start:18.0, duration:5.0, position:{x:0.18,y:0.82} }

# Chapter breaks — dip to black between sections
set_layer_audio_transitions { edge:{at:32},   style:"dip-to-black", duration:0.6 }
set_layer_audio_transitions { edge:{at:140},  style:"dip-to-black", duration:0.6 }

# On-device captions — Hindi audio → English subtitles
transcribe_audio            { layerId:"a-roll", language:"hi", translate:"en" }
add_caption_layer_from_audio { trackId:"a-roll", stylePreset:"classic-cinema", position:{x:0.5,y:0.86} }

# Vintage doc grade — slightly faded, warm
apply_global_color_grade { scope:"global", params:{ exposure:0, contrast:+0.18, saturation:-0.12, temp:+5, shadows:-0.06 } }
set_layer_lut             { layerId:"all", lut:"kodak-2393", intensity:0.55 }

# Tail card
add_text_layer { content:"Find Ramesh — Dadar West, 6am – 10am",
                 start:170, duration:10, fontSize:36, position:{x:0.5,y:0.5}, fontWeight:500 }
set_text_animation { layerId:"tail", inId:"fade-up", inDurationSec:0.8 }

# Loudness — broadcast spec
build_loudness_plan { target:"-23 LUFS EBU R128" }

set_export_settings { resolution:"1080p", codec:"h264", fps:30, bitrate:18 }
export_project      {}
04 · Music Video · 2:30 · 9:16

"Vertical music video for an unreleased indie-electronic single."

Performance footage cut to a 124 BPM track, mask-wipe section breaks, kinetic-typography lyric drops on the chorus, chromatic-aberration FX on the drop, warm-cool split grade.

# 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 — beatMs = 60000/124 = ~484ms; bar = 4 beats ≈ 1.94s
create_project          { name:"Indie Single", aspectRatio:"9:16", resolution:"1080p", fps:30 }
add_audio_layer          { uri:"single_master.wav", layerId:"track", start:0, duration:150, volume:1.0 }
add_video_layer × 12      # performance shots p0..p11

# Intro (0–18s) — slow-motion footage of artist, single mask reveal of name
add_text_layer     { content:"AYA", start:6.0, duration:6.0, fontSize:200, position:{x:0.5,y:0.5}, fontWeight:900 }
set_layer_mask     { layerId:"t0", shape:"rect", rect:{x:0,y:0,width:0,height:1}, feather:0.03 }
keyframe_add       { layerId:"t0", property:"mask.rect.width", timeMs:0,    value:0 }
keyframe_add       { layerId:"t0", property:"mask.rect.width", timeMs:1500, value:1, interp:{type:"easeOut"} }

# Verse cuts — every 2 beats (≈968ms) starting at 18000
for i in 0..14:  split_layer { layerId:"p0..", timeMs: 18000 + i*968 }

# Chorus (48–72s) — punchy half-beat cuts + zoom punch on the drop
for i in 0..47:  split_layer { layerId:"p_ch..", timeMs: 48000 + i*484 }
keyframe_add { layerId:"p_drop", property:"transform.scale", timeMs:47800, value:1.0, interp:{type:"easeIn"} }
keyframe_add { layerId:"p_drop", property:"transform.scale", timeMs:48000, value:1.22 }
keyframe_add { layerId:"p_drop", property:"transform.scale", timeMs:48484, value:1.0 }

# Chromatic-aberration FX bursts on the drop (48s, 60s, 120s)
set_layer_effect { layerId:"p_drop",  effect:"chromatic-aberration", intensity:0.5 }
keyframe_add     { layerId:"p_drop", property:"fx.intensity", timeMs:48000, value:0.5 }
keyframe_add     { layerId:"p_drop", property:"fx.intensity", timeMs:48800, value:0.0 }

# Kinetic-typography lyric drop — hook line, one word per half-beat
words = ["I", "DON'T", "REMEMBER", "ANYMORE"]
for i, w in enumerate(words):
    t0 = 48000 + i*484
    add_text_layer { content:w, start:t0/1000, duration:0.6, fontSize:140, position:{x:0.5,y:0.4}, fontWeight:900 }
    keyframe_add   { layerId:"lyr"+i, property:"transform.scale", timeMs:0,   value:0.0 }
    keyframe_add   { layerId:"lyr"+i, property:"transform.scale", timeMs:180, value:1.08, interp:{type:"easeOut"} }
    keyframe_add   { layerId:"lyr"+i, property:"transform.scale", timeMs:280, value:1.0 }

# Mask-wipe section break at the bridge (96s)
add_shape_layer   { kind:"rect", color:"#0a0a0f", start:96.0, duration:0.6, position:{x:0.5,y:0.5}, size:{w:1.0,h:1.0} }
keyframe_add      { layerId:"wipe", property:"transform.x", timeMs:0,   value:-0.6 }
keyframe_add      { layerId:"wipe", property:"transform.x", timeMs:600, value:1.0, interp:{type:"easeInOut"} }

# Split grade — verse cooler, chorus warmer
set_layer_color_adjust { layerId:"verses", temp:-10, saturation:-0.08, contrast:+0.10 }
set_layer_color_adjust { layerId:"choruses", temp:+8, saturation:+0.18, contrast:+0.18 }
set_layer_lut          { layerId:"all", lut:"cinematic-teal-orange", intensity:0.55 }

# Vignette to focus the eye on the performer
set_layer_effect { layerId:"all", effect:"vignette", intensity:0.35 }

set_export_settings { resolution:"1080p", codec:"h264", fps:30, bitrate:16 }
export_project      {}
05 · Tutorial / How-to · 90s · 9:16

"90-second tutorial on how to make pour-over coffee — step counter on screen."

Top-down screen-recording of the process, animated step counter (1/6, 2/6...), kinetic instruction text, countdown widget for the 30-second bloom, clean daylight grade.

# 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", resolution:"1080p", fps:30 }
add_video_layer × 6      # s0..s5 — one shot per step
add_audio_layer          { uri:"kitchen_amb.wav", volume:0.5 }
add_stock_music_layer    { query:"calm acoustic lo-fi 80 BPM", 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     { content: i+"/6", start:t0, duration:14.5, fontSize:42, position:{x:0.88,y:0.08}, fontWeight:800 }
    set_layer_border   { layerId:"step"+i, width:3, color:"#ffffff", cornerRadius:0.5 }
    set_text_animation { layerId:"step"+i, inId:"scale-pop", inDurationSec:0.3, outId:"fade", outDurationSec:0.25 }

# Instruction kinetic text — pop on each step start
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     { content:line, start:t0+0.3, duration:3.5, fontSize:56, position:{x:0.5,y:0.18}, fontWeight:700 }
    set_typewriter     { layerId:"ins"+i, charDelayMs:42 }
    set_text_animation { layerId:"ins"+i, outId:"slide-up", outDurationSec:0.4 }

# Countdown widget for the 30s bloom step (step 3, starts at 30s)
add_widget_layer { kind:"countdown", start:32.0, duration:30, durationMs:30000,
                   position:{x:0.5,y:0.5}, style:{ font:"Mono Bold", color:"#fbbf24", size:120 } }

# Clean daylight grade — bright, accurate, slight warmth
apply_global_color_grade { scope:"global", params:{ exposure:+0.18, contrast:+0.05, saturation:+0.15, temp:-3, highlights:-0.08 } }

# Light music swell on the last step
set_layer_volume_keyframes { layerId:"music", keyframes:[
    {timeMs:0,    volume:0.35},
    {timeMs:75000,volume:0.55},
    {timeMs:90000,volume:0.0}
]}

set_export_settings { resolution:"1080p", codec:"h264", fps:30, bitrate:12 }
export_project      {}
06 · Wedding Highlight · 75s · 16:9

"75-second wedding highlight cut from 4 hours of footage."

Emotional structure: morning, ceremony, first dance, party. Slow speed-ramp into the kiss, vows-as-VO with ambient music underscore, custom warm-cream LUT, sparkle Lottie overlay on the rings shot.

# Intake
Q1: Aspect & length? → 16:9 1080p 24fps (cinematic), 75s
Q2: Music?           → "Something soft, piano — under 90 BPM, no lyrics"
Q3: VO?              → Use a 25-second slice of the bride's vows
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", resolution:"1080p", fps:24 }
add_video_layer × 18 # curated highlights w0..w17
add_audio_layer     { uri:"bride_vows.wav", layerId:"vows", start:30, duration:25 }
add_stock_music_layer{ query:"emotional piano 84 BPM", layerId:"music", start:0, duration:75, volume:0.45 }

# Sparkle Lottie on the rings detail (w3, around 6s)
add_lottie_layer    { preset:"sparkles", start:5.5, duration:2.5, position:{x:0.5,y:0.5}, intensity:0.6 }

# Speed-ramp into the kiss — scale + slow opacity in last frames before
keyframe_add { layerId:"w_kiss", property:"transform.scale", timeMs:0,    value:1.0 }
keyframe_add { layerId:"w_kiss", property:"transform.scale", timeMs:1200, value:1.12, interp:{type:"easeInOut"} }
keyframe_add { layerId:"w_kiss", property:"transform.scale", timeMs:2400, value:1.06 }

# Cross-fade transitions between every clip in the slow sections
for edge in [(w0,w1),(w1,w2),(w2,w3),(w11,w12),(w12,w13)]:
    set_layer_audio_transitions { edge:edge, style:"crossfade", duration:0.6 }

# Custom warm-cream LUT (round-trip from a reference still)
write_cdl           { slope:[1.04,1.02,0.95], offset:[0.02,0.01,-0.01], power:[1.0,1.0,1.04], sat:0.96 }
bake_cdl_to_cube    { cdlId:"cdl_sara_adam", lutSize:33 }
lut_register_custom { name:"sara-adam-warm", cube:"..." }
set_layer_lut       { layerId:"all", lut:"sara-adam-warm", intensity:0.72 }
apply_global_color_grade { params:{ exposure:+0.10, contrast:+0.08, saturation:+0.05, temp:+3, shadows:+0.06 } }

# VO ducking — music drops under vows, returns smoothly after
set_layer_volume_keyframes { layerId:"music", keyframes:[
    {timeMs:0,    volume:0.45},
    {timeMs:30000,volume:0.45},
    {timeMs:30500,volume:0.18},
    {timeMs:54500,volume:0.18},
    {timeMs:55500,volume:0.55}
]}

# Tail card — names and date with elegant fade
add_text_layer     { content:"Sara & Adam", start:68.0, duration:7.0, fontSize:90, position:{x:0.5,y:0.45}, fontFamily:"Serif Display", fontWeight:400 }
add_text_layer     { content:"June 14, 2026", start:68.0, duration:7.0, fontSize:36, position:{x:0.5,y:0.56}, fontFamily:"Serif Display" }
set_text_animation { layerId:"name", inId:"fade-up", inDurationSec:1.0 }
set_text_animation { layerId:"date", inId:"fade-up", inDurationSec:1.0 }
keyframe_add       { layerId:"name", property:"opacity", timeMs:6000, value:1.0 }
keyframe_add       { layerId:"name", property:"opacity", timeMs:7000, value:0.0, interp:{type:"easeIn"} }

set_export_settings { resolution:"1080p", codec:"h264", profile:"high", fps:24, bitrate:20 }
export_project      {}
07 · Sports Hype Reel · 45s · 9:16

"45-second hype reel for a high-school basketball team's playoff push."

Aggressive 140 BPM cut with double-time bursts on the chorus, zoom-punch on dunks, glitch FX on the drop, big bold sponsor lower-third, high-contrast moody grade, glow-bordered final score graphic.

# Intake
Q1: Platform?      → IG Reel + TikTok cross-post, 9:16 1080p 30fps
Q2: Energy?        → "Maximum. Like a hype train hit a building."
Q3: Music?         → 140 BPM trap-style instrumental, no vocals

# beatMs = 60000/140 = ~429ms; double-time = 214ms

create_project          { name:"Wildcats Playoff", aspectRatio:"9:16", resolution:"1080p", fps:30 }
add_video_layer × 16     # dunks, blocks, threes, crowd
add_stock_music_layer    { query:"trap hype instrumental 140 BPM no vocals", start:0, duration:45 }

# Verse — beat-per-cut for 16 beats (0–6.9s)
for i in 0..15: split_layer { layerId:"v0..", timeMs: i*429 }

# Drop hits at 6900ms — glitch + flash + zoom punch
set_layer_effect           { layerId:"drop_clip", effect:"glitch", intensity:0.6 }
keyframe_add               { layerId:"drop_clip", property:"fx.intensity", timeMs:0,   value:0.6 }
keyframe_add               { layerId:"drop_clip", property:"fx.intensity", timeMs:200, value:0.0 }
keyframe_add               { layerId:"drop_clip", property:"transform.scale", timeMs:0,   value:1.0, interp:{type:"easeIn"} }
keyframe_add               { layerId:"drop_clip", property:"transform.scale", timeMs:120, value:1.25 }
keyframe_add               { layerId:"drop_clip", property:"transform.scale", timeMs:430, value:1.0 }
set_layer_audio_transitions { edge:{at:6900}, style:"flash", duration:0.08 }

# Chorus — double-time half-beat cuts (6.9–21s, 32 cuts)
for i in 0..31: split_layer { layerId:"ch0..", timeMs: 6900 + i*214 }
set_layer_audio_transitions { edge:{at:8000},  style:"whip-pan", duration:0.12 }
set_layer_audio_transitions { edge:{at:11000}, style:"zoom-blur", duration:0.14 }
set_layer_audio_transitions { edge:{at:14000}, style:"whip-pan", duration:0.12 }

# Big text — "WILDCATS" punch on drop
add_text_layer     { content:"WILDCATS", start:6.7, duration:1.4, fontSize:180, position:{x:0.5,y:0.5}, fontWeight:900 }
set_layer_border_glow{ layerId:"hype", color:"#ef4444", intensity:0.85, radius:0.06 }
keyframe_add       { layerId:"hype", property:"transform.scale", timeMs:0,   value:0.0 }
keyframe_add       { layerId:"hype", property:"transform.scale", timeMs:120, value:1.3, interp:{type:"easeOut"} }
keyframe_add       { layerId:"hype", property:"transform.scale", timeMs:240, value:1.0 }
keyframe_add       { layerId:"hype", property:"transform.rotation", timeMs:0,   value:-5 }
keyframe_add       { layerId:"hype", property:"transform.rotation", timeMs:240, value:0 }

# Moody high-contrast grade
apply_global_color_grade { scope:"global", params:{ exposure:-0.05, contrast:+0.30, saturation:-0.10, temp:-6, shadows:-0.08 } }
set_layer_lut             { layerId:"all", lut:"teal-blue-night", intensity:0.7 }

# Sponsor lower-third (last 5s)
add_lower_third_layer { preset:"lowerThird:sport-aggressive", content:"@ HighSchool Gym · Fri 7pm", start:40, duration:5 }

# Final score card with glow border
add_shape_layer      { kind:"rect", color:"#000000", start:42, duration:3, position:{x:0.5,y:0.5}, size:{w:0.7,h:0.25} }
set_layer_border_glow{ layerId:"score_bg", color:"#fbbf24", intensity:1.0, radius:0.08 }
add_text_layer       { content:"WILDCATS 78 — TIGERS 64", start:42, duration:3, fontSize:50, position:{x:0.5,y:0.5}, fontWeight:900 }

set_export_settings { resolution:"1080p", codec:"h264", fps:30, bitrate:14 }
export_project      {}
08 · Luxury Real-Estate Walkthrough · 60s · 16:9

"60-second luxury property listing for a Malibu home."

Steady slow camera moves, AE-style mask-wipe transitions between rooms, animated room-label callouts, agent lower-third, price-card reveal with parallax shadow, soft warm grade.

# 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", resolution:"1080p", fps:30 }
add_video_layer × 7      # r0..r6, one per room
add_stock_music_layer    { query:"ambient piano luxury 70 BPM", volume:0.5 }

# Slow push-ins everywhere — the room should feel like it's revealing itself
for clipId in [r0..r6]:
  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:"easeInOut"} }

# Mask-wipe transition between rooms — clean diagonal sweep
for edge in [(r0,r1),(r1,r2),(r2,r3),(r3,r4),(r4,r5),(r5,r6)]:
    set_layer_audio_transitions { edge:edge, style:"mask-wipe-diagonal", duration: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     { content:l, start:t0+0.4, duration:6.0, fontSize:48, position:{x:0.08,y:0.86}, fontFamily:"Serif Display", fontWeight:300, textAlign:"left", fullWidth:false }
    keyframe_add       { layerId:"lbl"+i, property:"transform.x", timeMs:0,    value:-0.3 }
    keyframe_add       { layerId:"lbl"+i, property:"transform.x", timeMs:600,  value:0.08, interp:{type:"easeOut"} }
    keyframe_add       { layerId:"lbl"+i, property:"opacity",    timeMs:5400, value:1.0 }
    keyframe_add       { layerId:"lbl"+i, property:"opacity",    timeMs:6000, value:0.0, interp:{type:"easeIn"} }

# Price card with parallax shadow
add_shape_layer      { kind:"rect", color:"#1a1a1a", start:52, duration:8, position:{x:0.5,y:0.5}, size:{w:0.6,h:0.4}, cornerRadius:0.04 }
set_layer_border     { layerId:"card", width:1, color:"#d4a056" }
add_text_layer       { content:"$12,450,000", start:52.5, duration:7.5, fontSize:72, position:{x:0.5,y:0.42}, fontFamily:"Serif Display", fontWeight:300 }
add_text_layer       { content:"5 BD · 7 BA · 8,400 SF", start:52.5, duration:7.5, fontSize:30, position:{x:0.5,y:0.52}, fontWeight:400 }
add_lower_third_layer { preset:"lowerThird:elegant-thin", content:"Maya Linden · Coldwell Banker · (310) 555-0182 · Open Sat 1–4pm", start:54, duration:6, position:{x:0.5,y:0.86} }

# Soft warm grade
apply_global_color_grade { scope:"global", params:{ exposure:+0.10, contrast:+0.08, saturation:+0.08, temp:+4, highlights:-0.05 } }

# Opening title
add_text_layer     { content:"22810 Pacific Coast Highway", start:0.5, duration:3.5, fontSize:52, position:{x:0.5,y:0.5}, fontFamily:"Serif Display", fontWeight:300 }
set_layer_mask     { layerId:"addr", shape:"rect", rect:{x:0,y:0,width:0,height:1}, feather:0.02 }
keyframe_add       { layerId:"addr", property:"mask.rect.width", timeMs:0,    value:0 }
keyframe_add       { layerId:"addr", property:"mask.rect.width", timeMs:1200, value:1, interp:{type:"easeOut"} }

set_export_settings { resolution:"1080p", codec:"h264", fps:30, bitrate:18 }
export_project      {}

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.

Prompts

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.

Console

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.

🛠️
Open the full MCP Console — a developer studio
Postman-style: browse the live tool catalog with schemas, send calls and inspect detailed responses, run multi-step recipes, and see the canvas the agent built — real frame and wireframe.
Open Console →
Not connected
FAQ

Questions, answered.

The essentials about the connector, privacy, and how agents stay current.

Does ExpoCut have a Model Context Protocol (MCP) server?
Yes. ExpoCut ships an in-app MCP server that exposes 225 typed tools. It is off by default and binds only to a local interface on the device — the user turns it on in Settings.
What can an AI agent actually do with it?
Create and open projects; add and trim video, image, audio, text, shape and widget layers; apply 130+ effects, 120+ preset filters and 49 transitions; add masks, gradient fade masks and light-leak overlays; generate on-device captions and text-to-speech voiceover; author keyframes, motion paths and advanced compositing; apply templates and brand profiles; inspect the canvas (describe_canvas, capture_canvas, preview_filmstrip); and export up to 4K — every step undoable.
How does an AI assistant connect to ExpoCut?
The user turns on the connector in ExpoCut's Settings. An MCP-compatible client (such as Claude) then connects over the local interface and calls the typed tool surface. No editing traffic is sent to ExpoTechIn servers.
How do agents discover the current capabilities?
Read llms.txt and llms-full.txt for a machine-readable map, or call the runtime introspection tools — 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?
ExpoCut is free during public beta, and the MCP server runs entirely on-device — the rendering and editing happen on your phone, not in the cloud.
Status

Live, on the device.

ExpoCut ships an in-app MCP server. When the user enables it, an MCP-compatible client like Claude Desktop binds to a local port on the device and can call the tools above. The server is off by default, runs only on a local interface, 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 →