ai.morphareels/morpha

MorphaReels

AI editor to build, animate & export layered short-form video projects via one tool catalog.

Endpointhttps://morphareels.ai/mcp (streamable-http)
Registry version1.0.395
Now100 tools · grade D (57) · pin ts_5dceaa36b1852ead05cb5d73421d89ae · protocol 2025-06-18
First / last listed2026-10-24 19:01 UTC / 2026-10-24 19:01 UTC
Grade over time2026-10-24: D (57), 100 tools

Is this your server? Own a server? ToolFit hosted watch re-checks it every 24 h ($0.25 per 30 days) or 6 h ($0.60) and sends you an HMAC-signed webhook when its tools or grade change: POST https://api.youraitools.co/toolfit/v1/watch. To opt out, see About & opt-out.

Timeline

First listing · 2026-10-24 19:01 UTC
100 tools, grade D (57)

Tools: list_projects list_workspaces create_workspace rename_workspace open_project show_project create_project move_project_to_workspace list_collection add_from_collection duplicate_project rename_project reid_project delete_project list_trash restore_project save_version list_versions restore_version rename_version delete_version upload_image upload_audio detect_text_regions safe_zones transcribe_clip clip_processing_status render_video render_status describe_video inspect_layers move_layer set_pivot add_keyframe set_keyframes_batch add_keyframes remove_keyframe shift_track set_track_loop add_image_layer add_video_layer add_shape add_curve duplicate_layer remove_layer reorder_layer set_style set_layer_fill set_text_background set_group_box add_color_keyframe remove_color_keyframe fade_layer set_layer_visible apply_preset apply_preset_stagger group_layers ungroup_layers set_group_parent rename_group add_to_collection remove_from_collection embed_project add_audio_overlay remove_audio_overlay update_audio_overlay set_video_layer_trim set_layer_block set_layer_transition set_embed_start shift_group set_group_window set_duration fit_duration_to_content cut_range set_embed_origins add_embed_origin remove_embed_origin set_custom_font list_fonts set_layer_text add_text_layer add_caption_track split_caption_line merge_caption_lines rename_layer set_loop set_canvas_size set_image_filename set_video_clip set_matte_source freeze_frame set_clip_speed add_speed_keyframe remove_speed_keyframe set_video_layer_muted add_page delete_page reorder_pages select_page

Current tools (100)

list_projectsList projects as { id, name, editorUrl } entries. The id is what every other tool's `projectId` argument takes. `name` is the human-readable label shown in the editor's project picker ("Untitled" when no name has been set; ids are opaque and the editor never shows them). `editorUrl` is a link that opens that project in the editor. By default lists the user's OWN personal projects (those not in any
params: workspaceId
list_workspacesList the workspaces (shared team spaces) the account belongs to, as { id, name, role, memberCount } entries. `role` is the caller's role in that workspace (owner / admin / editor / viewer) — only owner/admin/editor can add projects. The `id` is the `workspaceId` argument of create_project, move_project_to_workspace and list_projects; `name` is the label people see in the workspace switcher. Return
params: none
create_workspaceCreate a new workspace (a shared team space) owned by the caller, and return it as { id, name, role, memberCount }. Use this when the user wants a new place to group projects — then move_project_to_workspace files projects into it, and create_project can place new ones there directly. The caller becomes its owner. Requires an account with an email (workspace membership is email-based), so a keyles
params: name*
rename_workspaceRename an existing workspace. Requires an owner or admin role in it (the same roles that may change its membership); an editor or viewer is refused. Takes the workspace id from list_workspaces. Touches only the label — membership, projects and roles are unchanged.
params: workspaceId*, name*
open_projectGet a link that opens a project directly in the editor (the editor loads exactly this project from the URL), where the person can see the work, change it by hand and export it. Returns { name, editorUrl }; `name` is the project's label.
params: projectId*
show_projectShow the project in the chat as a live preview that plays and follows every edit; a multi-page project turns its pages in the preview. Call it once per project, right after creating or opening it; later edits appear in the preview without calling again. Answers { name, pages, placards, durationSeconds, editorUrl }. Its result also carries one picture per page, a Morpha placard shaped like that pag
params: projectId*, controls
create_projectCreate a new project and return its server-minted id. The id is an opaque v4 UUID assigned automatically, not chosen by the caller; people see the project's `name`. By default this mints a fresh BLANK project (empty canvas, no layers) — creation is independent from cloning. Pass `fromProjectId` ONLY when you want to clone: the new project then copies that project's JSON, uploaded assets, and uploa
params: fromProjectId, name, workspaceId
move_project_to_workspaceMove a project into a workspace, or back to the caller's personal space. Pass `workspaceId` (from list_workspaces) to place the project in that workspace — every member then sees it, with their workspace role deciding edit vs view. Pass `workspaceId: null` to detach it back to personal. Requires an edit-capable role (owner/admin/editor) in the TARGET workspace and write access to the project. Swit
params: projectId*, workspaceId*
list_collectionList the user's reusable Collection — layers they added with add_to_collection (lower-thirds, logo stings, brand intros, or any single layer). Returns { items: [{ scope, workspaceId, sourceProjectId, sourceProjectName, ownerEmail, elementId, name, kind, childCount }] }. `scope` is "personal" (from the user's own solo projects) or "team" (from a workspace they belong to). Show the user each item's
params: none
add_from_collectionAdd an item from the user's Collection into a project as a self-contained COPY. Pass the destination `projectId`, plus the `sourceProjectId` + `elementId` of an item from list_collection. Copies that layer (a whole group brings its children) plus its image/clip/font bytes into the destination at fresh ids and its original canvas position — fully detached, so it is immediately yours to edit and not
params: projectId*, sourceProjectId*, elementId*
duplicate_projectDuplicate an existing project into a brand-new one — a full copy of its layers, animations, styles, uploaded assets, and clips. Pass the SOURCE project's `projectId`; the copy gets a fresh id automatically (ids are opaque; people see the name). Defaults the copy's name to "<source name> copy" unless you pass `name`. WORKSPACE: the copy FOLLOWS THE SOURCE by default — a project in a workspace is co
params: projectId*, name, workspaceId
rename_projectUpdate a project's human-readable name (shown in the editor's project picker). Pass an empty string to clear the name and fall back to the id. Doesn't touch any layers, animations, or styles — only the picker label.
params: projectId*, name*
reid_projectChange a project's id LOSSLESSLY — re-keys its JSON, every saved version, and all assets + clips, then deletes the old id. Unlike create_project (clone), version history is preserved. Enforces the id policy: the new id is always a v4 UUID — pass `newId` to choose one, or omit to mint a fresh UUID. A project's `name` (the only user-facing label) is untouched, so the picker/editor are unchanged. The
params: projectId*, newId
delete_projectMove a project to Recently deleted. It leaves every project list at once and can be restored for 30 days, with restore_project or from the person's projects page; after that it is deleted for good, with its saved versions, uploaded images and clips. Returns { name, purgeAt, restorable: true }, where purgeAt is when it will be deleted for good (epoch ms). Deleting for good before then is the person
params: projectId*
list_trashList the projects in Recently deleted as { id, name, trashedAt, purgeAt, deletedBy, canEdit } entries. Each can be restored with restore_project until purgeAt (epoch ms), 30 days after it was deleted. `deletedBy` is { kind: "you" | "person" | "agent", label }: the caller, a teammate (label is their email), or a connected assistant (label is its name). `canEdit` says whether the caller may restore
params: workspaceId
restore_projectBring a project back from Recently deleted, with its versions, files, workspace and sharing as they were. Returns { projectId, name }. Scheduled social posts cancelled by the delete stay cancelled. Requires edit access, as delete_project does; take the id from list_trash.
params: projectId*
save_versionSave a version of the current project state. The person flicks between versions in the editor to compare or roll back, so one version per logical change-set (not one per tool call) is what they can revisit. `name` is shown verbatim in the version picker, so a short description reads best (e.g. "add slide-in animation").
params: projectId*, name
list_versionsList every saved version of a project, newest-first. Returns summaries (id, name, timestamp, source, kind, version_number, cp) — not the inner project JSON. `kind` is `"bookmark"` (deliberate save, gets a v<N> label, partner-pinnable in the <morpha-video> embed) or `"auto"` (editor auto-snapshot every ~10 mutations, restore-only). `version_number` is bookmark-only and stable for the lifetime of th
params: projectId*
restore_versionLoad a saved version over the live project. Accepts either the version's UUID `id` or the `v<N>` shorthand (e.g. `"v6"` finds the bookmark with version_number=6); auto-snapshots are addressable by UUID only. Without `page_index` this replaces the ENTIRE project (every page) — an auto checkpoint named `before restore: …` is saved first whenever the current state has unsaved changes, so the overwrit
params: projectId*, versionId*, page_index
rename_versionRename a saved version's human-readable label (what the user sees in the picker). The `v<N>` partner-facing identifier is unaffected — only the display name changes. Accepts the version's UUID `id` or the `v<N>` shorthand. Empty / whitespace-only names are rejected.
params: projectId*, versionId*, name*
delete_versionPermanently delete one saved version (bookmark or auto-snapshot). Accepts the version's UUID `id` or the `v<N>` shorthand. The bookmark v<N> sequence keeps gaps — numbers never re-shuffle — so any external snippet referencing the deleted `v<N>` will fail to resolve afterwards. Idempotent: deleting a non-existent version is a no-op.
params: projectId*, versionId*
upload_imageUpload an image into a project from a public http(s) URL or a file attached in the chat, fetched server-side. Pass `url` for a public http(s) link, or `file` for a file the person attached in the chat (a host that supports file inputs, such as ChatGPT, fills `file` with a temporary download link itself; do not ask the person for a URL to something they have attached). The target is the URL you pas
params: projectId*, url, file, name
upload_audioUpload an audio track into a project from a public http(s) URL or a file attached in the chat, fetched server-side — the ONLY way to get an audio file's bytes into a project over MCP/HTTP (add_audio_overlay / update_audio_overlay only reference a file that is already uploaded). Pass `url` for a public http(s) link, or `file` for a file the person attached in the chat (a host that supports file inp
params: projectId*, url, file, name
detect_text_regionsReturn the OCR text regions detected in a clip's video frames OR a still image asset. Pass either `clip` (a video.<id>.clip filename) or `image` (an image layer's filename) — not both. Video clips are sampled every 0.3s at upload time; images are OCR'd once. Results are cached in R2 next to the source. Returns { status: 'ready' | 'not-ready', frames: [{ frame, time, words: [{ text, x0, y0, x1, y1,
params: projectId*, clip, image
safe_zonesReturn horizontal 'safe band' y-ranges of the canvas where a clip's burned-in video text (subtitles, lower thirds, titles) does NOT appear, plus the inverse 'text bands' (where it does). Reads the OCR cache built at clip-upload time (see detect_text_regions). Coordinates returned in BOTH source pixels and CANVAS pixels — canvas coords already account for the video layer's fit/anchor/position. Use
params: projectId*, clip*, bandHeight, occupancyThreshold, minConfidence
transcribe_clipReturn the cached transcript of a clip's audio. Generated client-side in the Morpha editor (transformers.js Whisper) and cached in R2 next to the clip; if absent, it is produced when the clip is opened in the editor. Independent of the clip's video codec — runs on the audio track only, so it works on HEVC/AV1 clips that the OCR pipeline can't decode. Returns { ok: true, status: 'ready' | 'not-read
params: projectId*, clip*
clip_processing_statusReport whether a clip — or every video clip in the project, if `clip` is omitted — has been through the browser-side video-processing pipeline: proxy build, audio split, transcription, OCR (text regions). Processing never runs on the server. The preview proxy is mandatory: every processing run builds it, and the Morpha editor builds any missing proxy for every page's clip when the project is opene
params: projectId*, clip
render_videoRender the project to an MP4 on Morpha's servers, for an agent that has no browser to drive (a chat app, a Linux box). Returns { renderId, status: 'rendering', next } — call render_status with that renderId after about 30 seconds. It needs a subscription and draws on the account's render allowance, so a longer composition and a larger scale each use more of it. The editor renders the same MP4 with
params: projectId*, page, scale
render_statusCheck a server render started by render_video, and get its download link. Returns { ok: true, status: 'not-ready' } while it is still rendering (wait and call again), or { ok: true, status: 'ready', data: { downloadUrl, editorUrl, expiresAt, videoSeconds, scale } } once the MP4 is ready. `downloadUrl` is a plain https link needing no Morpha account — give it to the person; it stops working when th
params: renderId*
describe_videoStructural OVERVIEW of the composition (the table of contents) — canvas size, duration, platform_safe_area (the part of the canvas TikTok / Reels UI and Instagram's feed crop leave visible: keep text, captions, logos and buttons inside it; null when nothing is covered or cropped), the backdrop summary, and a z-ordered tree (top of stack first) of every layer with its elementId, type, name, type la
params: projectId*
inspect_layersFull per-element drill-in — the 'open this layer' half of the browser. Returns each named element's COMPLETE record: all of its own fields plus its animation tracks (every keyframe), colour/fill tracks, track-loop (extrapolation) modes, and style. Pass the elementIds you read from describe_video; pull detail only for the handful of layers you're about to mutate, not the whole project.
params: elementIds*, projectId*
move_layerSet a layer's static base transform. Writes x/y/w/h/rotation directly on image.<id>, video.<id>, and shapes.<id>; for group.<id> sets pivotX/pivotY (no width/height/rotation — use add_keyframe for group rotation). Also sets `scale` and `opacity`, which every layer kind carries. Note: when a layer has a keyframe track for a property, the track OVERRIDES the static value at every frame — use add_key
params: elementId*, x, y, width, height, rotation, scale, opacity, clear_animation, projectId*
set_pivotSet the rotation / scale pivot anchor for an image, video, shape, or text leaf. Picks one of the 9 standard bbox anchors — corners, edge midpoints, or centre — so the layer rotates and scales around that point instead of its centre. The pivot is normalized to the bbox, so resizing the layer keeps the pivot anchored to the same corner / edge / centre. Static (not animated). For groups, use move_lay
params: elementId*, anchor*, projectId*
add_keyframeAdd or overwrite a keyframe on a layer's animation track. For leaves (image/video/shape), x/y/rotation keyframes are ABSOLUTE canvas-space values: x and y are the layer centre's pixel position (canvas is 1080×1920), rotation is degrees. For groups, x/y keyframes are translation offsets applied around the group's frozen pivot, and rotation is the group's absolute angle. scale orbits the layer/pivot
params: elementId*, property*, frame*, value*, easing, projectId*
set_keyframes_batchAdd or overwrite MANY keyframes across MANY layers in ONE call — functionally equivalent to N add_keyframe calls but with one HTTP/MCP round-trip. Pass an array of entries; each entry has the same fields as add_keyframe. Validated atomically: any invalid entry rejects the whole batch. Use this whenever you'd call add_keyframe more than a couple of times (rippling grids, twinkling starfields, stagg
params: keyframes*, projectId*
add_keyframesAdd many keyframes to ONE element's ONE property in a single call, with an optional loop mode applied in the same call. The idiomatic form when every layer in a multi-element animation gets its own track (ripple dot pulse, snowflake fall, equaliser-bar wave). Factors elementId + property out of the loop body and folds set_track_loop in. Use set_keyframes_batch instead when you need to mix elements
params: elementId*, property*, keyframes*, loop, projectId*
remove_keyframeRemove the keyframe at frame N on a layer's track. Removing the last keyframe from a track restores the layer's static base value across the timeline.
params: elementId*, property*, frame*, projectId*
shift_trackBulk-shift every keyframe's VALUE on one property of one layer by `delta`. Mirrors the 'select all keyframes + nudge layer' gesture in After Effects / Premiere / FCP — preserves the relative spacing of the animation but slides the whole curve. Keyframe TIMES are untouched. Use for 'move all x by -30px' on a complex animation, retiming a fade by adjusting its base opacity, rotating an existing wobb
params: elementId*, property*, delta*, projectId*
set_track_loopSet the extrapolation mode for one property's animation track. Modes: "hold" (default — holds the boundary keyframe's value past the ends), "loop" (wraps frames past the last keyframe back to the first, restarting the animation), "ping-pong" (alternates direction each cycle, bouncing back and forth), "cycle" (wraps like loop but each cycle adds the boundary delta — used for endless rotation or scr
params: elementId*, property*, mode*, projectId*
add_image_layerAdd an image layer. The image must already be uploaded: upload_image takes a web URL or a file attached in the chat, and a file from the person's disk is uploaded in the editor. Every upload returns { filename, name, width, height }: pass `filename` (the stored file's id) here, and `name` as the layer's label. SIZE THE BOX TO THE PICTURE: pass `width` alone (or `height` alone) and the other side i
params: filename*, x*, y*, width, height, block, name, projectId*
add_video_layerAdd a video layer. The clip must already be uploaded; clips are uploaded in the MorphaReels editor. An upload returns { filename, name }: pass `filename` (the stored clip's id) as `clip`, and `name` as the layer's label. To duplicate an existing layer, reuse its clip filename — the editor auto-assigns a fresh id.
params: clip*, x*, y*, width*, height*, name, projectId*
add_shapeAdd a shape layer. `kind` selects the primitive: rect | ellipse | triangle | star | pentagon | hexagon | arrow | heart | rounded-rect | diamond | parallelogram | trapezoid | semicircle | ring | pill | cross | heptagon | octagon | star-4 | star-6 | sparkle | burst | arrow-left | double-arrow | chevron | block-arrow-up | curve | lightning | speech-bubble | location-pin | checkmark | x-mark | shield
params: kind*, x, y, width, height, color, block, projectId*
add_curveDraw an editable arrow / curved line — a stroked quadratic bezier with an arrowhead. Specify the two endpoints (x1,y1)→(x2,y2) in canvas pixels; `bend` pushes the midpoint perpendicular (px, 0 = straight line, positive/negative curves either way). `color` #rrggbb, `stroke_width` px, `arrow_head` none|end|both (default end). Use this for callout arrows (e.g. swooping into a link).
params: x1*, y1*, x2*, y2*, bend, color, stroke_width, arrow_head, projectId*
duplicate_layerComposition primitive: clone a leaf (image.<id> / video.<id> / shapes.<id> / text.<id>) `count` times, applying a cumulative per-step transform — copy i is offset by i·(dx,dy) px, rotated by i·d_rotation°, and scaled by d_scale^i. One call instead of dozens: a circle of stars (dx/dy + d_rotation), a row of chevrons (dx), a fractal (d_scale<1 + d_rotation), a staggered grid. Styles are copied; anim
params: elementId*, count*, dx, dy, d_rotation, d_scale, projectId*
remove_layerDelete a video, image, text, or shape LEAF layer. Errors on a group.<id> (dissolve it with ungroup_layers instead) and on a pinned layer such as the canvas backdrop. Deleting a video layer also takes everything welded to that clip: its welded audio overlay(s) and its welded caption lines, both of which derive their timing from the clip's trim and have no meaning without it.
params: elementId*, projectId*
reorder_layerMove a layer within its parent's siblings. newIndex is 0-based among siblings (root list when ungrouped, or the parent group's children when nested). 0 = bottom of that subtree; last = top.
params: elementId*, newIndex*, projectId*
set_styleSet style fields on a layer. Only the fields you pass are changed; omit a field to leave it untouched. Covers border/radius/shadow (including borderAlign — inner|center|outer border position) plus image-only fields: fit (stretch|cover|contain), anchorX/anchorY (0..1, where the source anchors when cropping/letterboxing under cover/contain), tintColor (#rrggbb) + tintStrength (0..1) for a colour ove
params: elementId*, borderRadius, borderWidth, borderColor, borderAlign, boxShadow, fit, anchorX, anchorY, tintColor, tintStrength, alphaMask, chroma_key, blend_mode, projectId*
set_layer_fillSet a layer's fill. The canvas backdrop is the pinned is_background image_layer (its element id is exposed via describe_video as background.elementId; the literal 'background.canvas' is also accepted as a synonym); null is rejected on the backdrop. Shapes require a Fill (null/missing is rejected). Image / video / text / group layers accept a Fill object (or `#rrggbb` hex) to paint a backdrop, or `
params: elementId*, fill*, clear_animation, projectId*
set_text_backgroundAdd or update the rounded background box behind a TEXT layer (text.<id>) in one call — sets the backdrop fill plus the box's padding, corner radius, and optional stroke. Pass only the fields you want to change. New text layers are already text_autofit "hug", so padding alone shrink-wraps the box to the text — ideal for caption / sticker chips; set_layer_text(text_autofit:"hug") is only needed when
params: elementId*, fill, padding, cornerRadius, strokeWidth, strokeColor, clear_animation, projectId*
set_group_boxSet a group's backdrop rect size. The rect is centred on (pivotX, pivotY) in group-local space and transforms with the group. Either dimension at 0 hides the backdrop entirely.
params: elementId*, box_width*, box_height*, projectId*
add_color_keyframeAdd or overwrite a colour keyframe on a fill track. Targets a leaf (shapes.<id>, image.<id>, video.<id>, group.<id>). The canvas backdrop is the pinned is_background image_layer (the literal 'background.canvas' is accepted as a synonym for its element id). The value is a Fill — adjacent keyframes crossfade stop-by-stop. 30 fps; frame is 0-indexed.
params: elementId*, property*, frame*, value*, easing, projectId*
remove_color_keyframeRemove the colour keyframe at an exact frame on a fill track. No-op when no track or no matching keyframe exists. Removing the last keyframe drops the track entry.
params: elementId*, property*, frame*, projectId*
fade_layerFade a layer's opacity between two frames in one call.
params: elementId*, fromFrame*, toFrame*, fromOpacity*, toOpacity*, projectId*
set_layer_visibleShow or hide a layer instantly by writing a single opacity keyframe (1 or 0) at frame 0.
params: elementId*, visible*, projectId*
apply_presetApply a canned animation preset to a layer.
params: elementId*, preset*, startFrame, projectId*
apply_preset_staggerApply the same preset to a LIST of layers with a per-element startFrame offset — one call instead of N apply_preset calls. For diagonal pop-in grids, sequential list reveals, ring-pulse sweeps. The startFrame for entry i is `startFrame + i * stagger`. Order the elementIds in the visual order you want the animation to cascade.
params: elementIds*, preset*, startFrame, stagger, projectId*
group_layersWrap sibling elements in a new group, which the Layers panel shows as one named row the person can select, move and animate as one thing (a butterfly assembled from wings + body + antennae, an icon built from primitives, a card + its title + badge, a lower-third). `name` is that row's label. The group composes its x/y/scale/rotation/opacity onto its descendants (so one track flies/spins/fades the
params: elementIds*, name, projectId*
ungroup_layersDissolve a group: its children are spliced into the group's parent at the group's old position. The group's animation tracks are discarded — children survive at their last positions but inherit none of the group's keyframes.
params: groupId*, projectId*
set_group_parentMove an element into a group (or out to root). Refuses to place a group inside its own descendants.
params: elementId*, parentGroupId*, index, projectId*
rename_groupRename a group. Pure cosmetic — labels appear in the Inspector and describe_video output.
params: groupId*, name*, projectId*
add_to_collectionAdd a layer to the user's reusable Collection. Pass ANY element id — a leaf (text.<id>, image.<id>, …) or a whole group.<id> (a lower-third, logo sting, brand intro). It then appears in the user's Collection (list_collection), where they — and, if this project is in a workspace, every teammate — can drop a self-contained COPY of it into any other project (add_from_collection). Copies are IMMUTABLE
params: elementId*, projectId*
remove_from_collectionRemove a layer from the user's Collection so it's no longer offered for reuse. Pass the element id that was added with add_to_collection. Copies already placed in other projects are unaffected (they're self-contained). No-op if the id isn't in the collection.
params: elementId*, projectId*
embed_projectEmbed another of the user's projects ("a morpha") inside this one as a version-pinned band. The source's layers are inlined into the host as a collapsible group, re-keyed to fresh ids, pinned to one immutable version of the source. Pass the source project's id as source_morpha_id (and optionally a version label); the server resolves and inlines the pinned version. Editing the band's inner layers o
params: source_morpha_id*, version, projectId*
add_audio_overlayAdd an audio overlay (mp3/m4a/wav/ogg) scheduled at a frame-aligned start. The asset must already be uploaded (upload_audio, or the editor for a file from the person's disk); pass the upload's returned `filename`, and its `name` as the track's label. 30 fps; convert seconds with frames = round(s * 30). Plays in the editor preview and is mixed into the MP4 export.
params: filename*, name, startFrame*, gain, fadeInFrames, fadeOutFrames, endFrame, sourceLayerId, projectId*
remove_audio_overlayDelete an audio overlay by id.
params: id*, projectId*
update_audio_overlayPatch an existing audio overlay. Only the fields you pass are changed. Pass endFrame:null to clear it (revert to natural-length playback).
params: id*, filename, name, startFrame, gain, fadeInFrames, fadeOutFrames, endFrame, sourceLayerId, denoiseStrength, projectId*
set_video_layer_trimPatch a video layer's trim window: source_in_frame (frame in source to start), source_out_frame (frame in source to stop, or null for natural end), timeline_start_frame (where on the project timeline the slice begins). Only the fields you pass are changed. Use this to clip out a segment of a source mp4: duplicate the layer first (in the editor) so you have two pointing at the same clip, then set d
params: elementId*, source_in_frame, source_out_frame, timeline_start_frame, projectId*
set_layer_blockSet (or replace) a layer's timeline BLOCK — the [start, start+duration) window it exists for. The layer is drawn ONLY inside that window, and its animation keyframes are sampled RELATIVE to the block start, so moving or trimming the block re-anchors its intro instead of leaving it behind. This is how a layer 'starts' at a point like an iMovie clip rather than being present for the whole compositio
params: elementId*, start*, duration*, projectId*
set_layer_transitionSet how a layer ENTERS at the start of its on-timeline window and LEAVES at the end, instead of popping. The transition is EDGE-RELATIVE — only a length and a look are stored — so it rides the edge through every later trim, slide or clip retime. Prefer this over fade_layer / apply_preset whenever the intent is 'enters and leaves nicely': those write opacity keyframes at ABSOLUTE frames, which stra
params: elementId*, edge*, kind*, frames, curve, direction, projectId*
set_embed_startPlace an embedded morpha band on the host timeline: set its TIME ORIGIN (the frame it starts). The band's whole inner reel plays relative to this frame, so its intro animations fire when the band appears instead of at 0:00 (the fix for 'the embedded intro doesn't animate'). Keeps the band's current window length; if it had none, the band spans from start to the composition end. Pass the band group
params: bandId*, start*, projectId*
shift_groupMOVE a group and everything inside it along the timeline, keeping its internal timing intact — the 'slide this whole section later' operation. A plain group is a relative CONTAINER: it has no window of its own, so on the timeline it spans the hull of its contents, and moving it slides the whole subtree as one rigid body. `start` is the ABSOLUTE frame the group's window should end up at, not a delt
params: elementId*, start*, projectId*
set_group_windowTRIM a group's own visible window — the [start, start+duration) range over which the group and its subtree are drawn. The contents are NOT moved or deleted: this clips what is shown, so use it to hide the head or tail of a whole section. Writing a window overrides the group's derived contents-hull from then on. Two safety corrections apply automatically: the group's OWN keyframes are compensated f
params: elementId*, start*, duration*, projectId*
set_durationAuthor an EXPLICIT composition length in seconds, pinning it (duration_authored=true) so the auto-fit no longer drives it. Morpha normally DERIVES the comp length from content (the furthest keyframe / video window / audio end); this overrides that with a fixed length — the stage becomes a fixed canvas you author into, and content past the end is kept but not played or exported. 1-second floor, no
params: seconds*, projectId*
fit_duration_to_contentClear an authored composition length and return to AUTO-FIT — the comp length tracks the furthest content (keyframe / video window / audio end) again, with a 1-second floor. The inverse of set_duration. NOTE: headless (no loaded media) this can UNDER-fit when a video layer's source_out_frame is null — its natural length is unmeasurable, so it contributes only its start frame; the length self-corre
params: projectId*
cut_rangeRipple-delete a time window [startFrame, endFrame) — remove that span and pull all later content earlier by delta = endFrame - startFrame (the NLE 'ripple delete' / 'close gap'). Shifts every keyframe, colour keyframe, marker, audio overlay, loop region, and start_at through the cut (a speed ramp is anchored to its clip and rides along unchanged), and is SOURCE-AWARE for video layers: a clip that
params: startFrame*, endFrame*, projectId*
set_embed_originsReplace the project's embed allowlist — the hostnames permitted to load this project through the public <morpha-video> embed. Pass the full desired list; it overwrites the previous one. An empty array turns embedding OFF (the public embed endpoint 404s the project). Each entry is normalized to a bare lowercased hostname (scheme, port, and path stripped, e.g. "https://example.com/x" → "example.com"
params: origins*, projectId*
add_embed_originAdd one hostname to the project's embed allowlist (the hostnames permitted to load the public <morpha-video> embed). Idempotent — re-adding an existing entry is a no-op. The origin is normalized to a bare lowercased hostname (scheme/port/path stripped). Morpha sends that website no request; any page on it may then load the project. Reference: https://morphareels.ai/docs/tools#addembedoriginorigin
params: origin*, projectId*
remove_embed_originRemove one hostname from the project's embed allowlist. Idempotent — removing an entry that isn't present is a no-op. Removing the last entry turns embedding OFF (the public embed endpoint 404s the project). Morpha sends that website no request; a page on it stops loading the project. Reference: https://morphareels.ai/docs/tools#removeembedoriginorigin
params: origin*, projectId*
set_custom_fontRegister a typeface Morpha does NOT ship, so text layers can use it by family name via font_family (exactly like a built-in family). Families already in the built-in catalogs (anything list_fonts returns from google/bunny/fontshare/fontsource/velvetyne) are REJECTED — they need no registration; just set font_family to them directly. `src` is EITHER a full URL (https://…) OR a font file (.woff2/.wo
params: family*, src*, weight, style, projectId*
list_fontsList available font families across every source the editor knows about (Google + Bunny + Fontshare + Fontsource + Velvetyne) PLUS the project's user-uploaded custom_fonts (surfaced as source: "custom"). Use this to discover families before set_layer_text / add_text_layer when you don't know what to pick. Returns { fonts: [{family, source, weights, italics}], total, returned, sources }. Filter via
params: q, source, limit, projectId*
set_layer_textEdit an existing text layer (text.<id>). Patches its text content, font, size, colour, and full type styling — pass only the fields you want to change. Does NOT create layers and does NOT touch image layers; use add_text_layer to make a new one. `font_family` is a Google Fonts family name (e.g. "Anton", "Bebas Neue"). `text_size` is the font size in px (omit to keep the current size). `text_color`
params: elementId*, text, text_size, font_family, text_color, font_weight, font_style, text_transform, letter_spacing, curve, line_height, text_align, text_autofit, text_valign, stroke_width, stroke_color, text_shadow, decorations, projectId*
add_text_layerCreate a new text layer — a first-class leaf that animates, groups, and z-orders exactly like an image or shape. The renderer draws live typeset text (multi-line, auto-fit to the box). Defaults: x/y = canvas centre, width 900, height 320, font_family "Hanken Grotesk", text_size derived from existing text layers (or ~10% of canvas height). Also accepts full type styling: font_weight (100-900), font
params: text, x, y, width, height, font_family, text_size, text_color, font_weight, font_style, text_transform, letter_spacing, curve, line_height, text_align, text_autofit, text_valign, stroke_width, stroke_color, text_shadow, decorations, block, projectId*
add_caption_trackBuild a caption track from pre-timed lines (e.g. derived from transcribe_clip's word timings). mode "line-sync" (default) creates one text layer per line, each shown only during its [startFrame, endFrame) window via hold-eased opacity keyframes — the active-line karaoke read; mode "static" makes a single layer with all lines joined. `style` picks a preset look. Lines default to a lower-third band.
params: lines*, mode, style, x, y, width, height, clip_element_id, projectId*
split_caption_lineSplit one caption line into two at a COMPOSITION frame strictly inside its window (in the editor this is the playhead). The right half is a full clone — style, band geometry, weld — and the text divides at the word gap nearest the split point (a single-word line keeps its text on the left; the right half starts empty). A welded line stays welded on both halves (the frame converts to the clip's sou
params: elementId*, atFrame*, projectId*
merge_caption_linesMerge two or more caption lines into one. The earliest line survives with the union window and the time-ordered texts joined by spaces; the others are removed. All lines must share one flavour — every one welded to the SAME clip, or every one standalone — and no other caption line on that track may sit inside the merged span (move or include it first).
params: elementIds*, projectId*
rename_layerSet the human-readable name of a video / image / shape / text layer — the label shown in the Inspector, and the basis for the layer's auto-derived <morpha-video> embed attribute (so renaming a layer to "caption" makes the embed attribute `caption`). Pass an empty string to clear the name. For groups use rename_group.
params: elementId*, name*, projectId*
set_loopSet the project's loop section: the whole composition repeats once per value, with one field of one layer varying across the repeats. Builds one pass per value, each setting `field` of `elementId` to that value — e.g. a caption text layer cycling through several strings. Pass an empty `values` array to clear the loop (the comp plays once).
params: elementId*, field, values*, projectId*
set_canvas_sizeResize the ACTIVE page's canvas to width × height pixels. The composition is scaled UNIFORMLY to fit the new frame (a single factor s = min(newW/oldW, newH/oldH), so nothing distorts — a circle stays a circle) and then re-centred so the old composition centre maps to the new canvas centre. Every layer's position, size, group pivots, and x/y/width/height keyframes follow this fit+recentre; same-asp
params: width*, height*, projectId*
set_image_filenameRepoint an existing image layer at a different uploaded asset — keeps the layer's id, position, size, animations, and styles; only the bitmap changes. The image must already be uploaded (upload_image for a web URL or a file attached in the chat; the editor for a file from the person's disk); pass the `filename` the upload returned, and its `name` as the label. Use this to swap a layer's image WITH
params: elementId*, filename*, name, projectId*
set_video_clipRepoint an existing video layer at a different uploaded clip — keeps the layer's id, position, size, animations, styles, and trim window; only the source mp4 changes. The clip must already be uploaded (the editor's Add video); pass the `filename` the upload returned, and its `name` as the label. Use this to swap a video layer's source WITHOUT losing its keyframes.
params: elementId*, clip*, name, projectId*
set_matte_sourceSet (or clear) a track matte — the host shows only where the matte source is opaque. The HOST can be a leaf (image.<id>, video.<id>, shapes.<id>, text.<id>) OR a group.<id> (a group is a layer of sorts): a group host clips ALL its composited children to the source shape's path — e.g. a marching chevron strip + black backing shown only inside an arrow / band shape. For a leaf host the source can be
params: elementId*, matte_source_id*, matte_inverted, projectId*
freeze_frameFreeze the picture at a frame: the clip is CUT there and a still of that frame is inserted between the halves, pushing everything after it later (the NLE 'frame hold'). This is how you hold a moment — play, freeze, continue. The still is an ordinary IMAGE layer showing that frame for `holdFrames` (default 150 = 5s at 30fps), so you resize, split, move or delete it like any other layer. `image` is
params: elementId*, frame*, image*, holdFrames, projectId*
set_clip_speedPlay a clip slower or faster at a CONSTANT rate — the normal way to retime a clip. 1 = source speed, 0.5 = half speed, 2 = double speed; range [0.1, 8]. The trim is unchanged, so the clip's length on the timeline changes to suit: at 0.5x it occupies twice as many frames, at 2x half as many. Audio is time-stretched with pitch preserved. Use add_speed_keyframe instead only when the rate must CHANGE
params: elementId*, speed*, projectId*
add_speed_keyframeAdd or overwrite a speed-ramp (time-remap) keyframe on a video layer, for a rate that CHANGES over the clip. `frame` is a PROJECT-timeline frame and must sit on the clip (at or after its timeline_start_frame); the curve itself is anchored to the clip, so moving the clip carries the ramp with it and never changes its duration. For a constant slower/faster clip use set_clip_speed instead. `rate` is
params: elementId*, frame*, rate*, projectId*
remove_speed_keyframeRemove the speed-ramp keyframe at `frame` (a PROJECT-timeline frame — the same value add_speed_keyframe and inspect_layers report) on a video layer. Removing the last keyframe clears the speed_keyframes array entirely (restoring 1× playback).
params: elementId*, frame*, projectId*
set_video_layer_mutedMute or unmute a video layer's baked audio (silenced in both preview and export). The processing pipeline's audio-split step sets this true after demuxing the clip's audio into a standalone overlay track (NLE-style linked A/V), so the source audio doesn't double with the overlay. Pass muted:false to restore the baked audio.
params: elementId*, muted*, projectId*
add_pageAppend a page to the project — works on any project, turning a single-page video into a multi-page one. Without duplicate_index a blank page is appended, sized to the project's canvas. With duplicate_index the page at that position is deep-copied (a fresh id is minted). There is no limit on page count. The new page becomes the active page; its index is returned. Versions or options of one video (s
params: name, duplicate_index, projectId*
delete_pageRemove the page at `index`. Fails on an out-of-range index or when only one page remains — a project must keep at least one page. The active page stays active; when the active page itself is deleted, active_index falls to the neighbouring page (the one that slid into its position, or the new last page).
params: index*, projectId*
reorder_pagesMove a page from `from_index` to `to_index`. The remaining pages shift to fill the gap; active_index is rewritten so it keeps pointing at the same page it did before the move. Fails on out-of-range indices.
params: from_index*, to_index*, projectId*
select_pageSwitch which page is ACTIVE — the page the content tools target. Subsequent describe_video / inspect_layers / all content tools read and write this page until the active page changes again. Pages are addressed by 0-based index from describe_video's pages block. Selecting the already-active page is a harmless no-op; fails on an out-of-range index.
params: index*, projectId*

Entries describe changes in tool definitions. They are not statements about a server, its quality or its author. The grade measures how the tool definitions fit model providers (token cost, schema compatibility, confusable names) as scored by the ToolFit engine at the time of the listing. JSON: append .json. Opt-out: how or hello@youraitools.co.