{"$schema":"https://wellknown.network/schemas/agent-record-v1.json","schemaVersion":"1","id":"ag_w373h3f9nczs","handle":"morpha","url":"https://wellknown.network/agents/morpha","links":{"self":"https://wellknown.network/agents/morpha/record.json","html":"https://wellknown.network/agents/morpha","markdown":"https://wellknown.network/agents/morpha/record.md","api":"https://wellknown.network/api/v1/agents/morpha","status":"https://wellknown.network/api/v1/agents/morpha/status","claim":"https://wellknown.network/agents/morpha/claim","claimApi":"https://wellknown.network/api/v1/claims","claimDescriptor":"https://wellknown.network/agents/morpha/claim.json","badge":"https://wellknown.network/agents/morpha/badge.svg","openapi":"https://wellknown.network/openapi.json","history":"https://wellknown.network/api/v1/agents/morpha/history","tools":"https://wellknown.network/api/v1/agents/morpha/tools"},"ard":{"identifier":"urn:air:morphareels.ai:server:morpha","type":"application/mcp-server-card+json"},"kind":"mcp_server","declared":{"name":"Morpha","summary":"AI editor to build, animate & export layered short-form video projects via one tool catalog.","description":"AI editor to build, animate & export layered short-form video projects via one tool catalog.","publisher":{"name":"ai.morphareels","url":null},"homepage":null,"repository":null,"version":"1.0.375","license":null,"protocols":["mcp"],"tags":[],"pricing":null,"endpoints":[{"url":"https://morphareels.ai/mcp","type":"mcp_streamable_http","auth":null,"probeable":true}],"skills":null,"tools":null,"extra":{"updatedAt":"2026-09-30T19:47:01.741473Z","publishedAt":"2026-09-30T19:47:01.741473Z","registryName":"ai.morphareels/morpha"},"attribution":{"kind":"mcp_registry","name":"mcp_registry","summary":"mcp_registry","version":"mcp_registry","description":"mcp_registry","publisherName":"mcp_registry"}},"derived":{"capabilities":[{"slug":"media.speech-recognition","name":"Speech Recognition","confidence":1,"provenance":"derived"},{"slug":"commerce.ecommerce","name":"E-commerce Operations","confidence":0.897,"provenance":"derived"},{"slug":"media.video-editing","name":"Video Editing","confidence":0.54,"provenance":"derived"},{"slug":"dev.ci-cd","name":"CI/CD & Deploy","confidence":0.525,"provenance":"derived"},{"slug":"productivity.crm","name":"CRM & Sales","confidence":0.525,"provenance":"derived"},{"slug":"content.writing","name":"Writing & Editing","confidence":0.51,"provenance":"derived"},{"slug":"infra.browser-automation","name":"Browser Automation","confidence":0.51,"provenance":"derived"}],"categories":["commerce","content","dev","infra","media","productivity"],"language":"en"},"observed":{"status":"live","statusReason":"Responded 4h ago.","lastOkAt":"2026-10-10T10:27:28.264Z","lastProbedAt":"2026-10-10T10:27:28.264Z","statusComputedAt":"2026-10-10T10:29:51.330Z","reliability30d":{"probes":110,"successRate":1,"p50Ms":38,"basis":"service","measures":{"availability":"availability","latency":"response time","tools":"tool surface observed","summary":"Checks reached the service itself."},"checks":{"total":108,"ok":108,"authBoundaryOk":0,"serviceOk":108,"note":"Counted from the observation rows for the window, checks of the server only (HTTP, A2A card, MCP initialize). ok = authBoundaryOk + serviceOk. `probes` is the sum of daily rollups and includes registry checks, so it can differ from `total`."}},"latestObservations":[{"at":"2026-10-10T10:27:28.264Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":29,"error":null,"detail":{"tools":[{"name":"list_projects","description":"List 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 "},{"name":"list_workspaces","description":"List the workspaces (shared team spaces) the account belongs to, as { id, name, role, memberCount } entries. `role` is the caller's role in that workspace (owne"},{"name":"create_workspace","description":"Create 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 "},{"name":"rename_workspace","description":"Rename 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 w"},{"name":"open_project","description":"Get a tappable link that opens a project directly in the editor (the editor loads exactly this project from the URL). Returns { name, editorUrl }. Use this when"},{"name":"create_project","description":"Create a new project and return its server-minted id. The id is an opaque v4 UUID assigned automatically — you never choose or name it; refer to the project by "},{"name":"move_project_to_workspace","description":"Move 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 — ever"},{"name":"list_collection","description":"List the user's reusable Collection — layers they added with add_to_collection (lower-thirds, logo stings, brand intros, or any single layer). Returns { items: "},{"name":"add_from_collection","description":"Add 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"},{"name":"duplicate_project","description":"Duplicate 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 `proje"},{"name":"rename_project","description":"Update 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"},{"name":"reid_project","description":"Change 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), versio"},{"name":"delete_project","description":"Permanently delete a project: its JSON, shortlist, versions, uploaded assets, and uploaded clips. Requires edit access — the owner, a workspace editor/admin/own"},{"name":"save_version","description":"Save a version of the current project state. ALWAYS call this once after each meaningful set of changes you make on the user's behalf — the user flicks between "},{"name":"list_versions","description":"List every saved version of a project, newest-first. Returns summaries (id, name, timestamp, source, kind, version_number, cp) — not the inner project JSON. `ki"},{"name":"restore_version","description":"Load 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"},{"name":"rename_version","description":"Rename 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 cha"},{"name":"delete_version","description":"Permanently 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"},{"name":"upload_image","description":"Upload an image into a project by fetching a public http(s) URL server-side. The target is the URL you pass and nothing else: Morpha's Worker fetches it with a "},{"name":"upload_audio","description":"Upload an audio track into a project by fetching a public http(s) URL server-side — the ONLY way to get an audio file's bytes into a project over MCP/HTTP (add_"},{"name":"create_upload_link","description":"Put a file from the caller's own disk into a project: an image (.png/.jpg/.jpeg/.gif/.webp/.svg), an audio track (.mp3/.m4a/.wav/.ogg/.aac) or a font (.woff2/.w"},{"name":"detect_text_regions","description":"Return 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"},{"name":"safe_zones","description":"Return 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 'te"},{"name":"transcribe_clip","description":"Return 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 absen"},{"name":"clip_processing_status","description":"Report 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, aud"},{"name":"render_video","description":"Render 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', "},{"name":"render_status","description":"Check 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 a"},{"name":"describe_video","description":"Structural OVERVIEW of the composition (the table of contents) — canvas size, duration, platform_safe_area (the part of the canvas TikTok / Reels UI and Instagr"},{"name":"inspect_layers","description":"Full 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 tr"},{"name":"move_layer","description":"Set 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/he"},{"name":"set_pivot","description":"Set 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 — "},{"name":"add_keyframe","description":"Add 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 t"},{"name":"set_keyframes_batch","description":"Add 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"},{"name":"add_keyframes","description":"Add 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"},{"name":"remove_keyframe","description":"Remove 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."},{"name":"shift_track","description":"Bulk-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 "},{"name":"set_track_loop","description":"Set the extrapolation mode for one property's animation track. Modes: \"hold\" (default — holds the boundary keyframe's value past the ends), \"loop\" (wraps frames"},{"name":"add_image_layer","description":"Add an image layer. The asset must already be uploaded (the editor's drag-drop, upload_image, create_upload_link, or POST /api/upload-asset/<projectId> with the"},{"name":"add_video_layer","description":"Add a video layer. The clip must already be uploaded (the morphareels-sdk's client.addVideo, or the editor). An upload returns { filename, name }: pass `filenam"},{"name":"add_shape","description":"Add a shape layer. `kind` selects the primitive: rect | ellipse | triangle | star | pentagon | hexagon | arrow | heart | rounded-rect | diamond | parallelogram "},{"name":"add_curve","description":"Draw 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 t"},{"name":"duplicate_layer","description":"Composition primitive: clone a leaf (image.<id> / video.<id> / shapes.<id> / text.<id>) `count` times, applying a cumulative per-step transform — copy i is offs"},{"name":"remove_layer","description":"Delete 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 back"},{"name":"reorder_layer","description":"Move 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"},{"name":"set_style","description":"Set 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 — inne"},{"name":"set_layer_fill","description":"Set 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 liter"},{"name":"set_text_background","description":"Add 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 optiona"},{"name":"set_group_box","description":"Set 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 ba"},{"name":"add_color_keyframe","description":"Add 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_backg"},{"name":"remove_color_keyframe","description":"Remove 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 ent"},{"name":"fade_layer","description":"Fade a layer's opacity between two frames in one call."},{"name":"set_layer_visible","description":"Show or hide a layer instantly by writing a single opacity keyframe (1 or 0) at frame 0."},{"name":"apply_preset","description":"Apply a canned animation preset to a layer."},{"name":"apply_preset_stagger","description":"Apply 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"},{"name":"group_layers","description":"Wrap sibling elements in a new group. USE THIS whenever several layers are one thing — a butterfly assembled from wings + body + antennae, an icon built from pr"},{"name":"ungroup_layers","description":"Dissolve 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"},{"name":"set_group_parent","description":"Move an element into a group (or out to root). Refuses to place a group inside its own descendants."},{"name":"rename_group","description":"Rename a group. Pure cosmetic — labels appear in the Inspector and describe_video output."},{"name":"add_to_collection","description":"Add 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 i"},{"name":"remove_from_collection","description":"Remove 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 "},{"name":"add_morpha_layer","description":"Embed 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"},{"name":"add_audio_overlay","description":"Add an audio overlay (mp3/m4a/wav/ogg) scheduled at a frame-aligned start. The asset must already be uploaded (upload_audio, create_upload_link, or the editor);"},{"name":"remove_audio_overlay","description":"Delete an audio overlay by id."},{"name":"update_audio_overlay","description":"Patch an existing audio overlay. Only the fields you pass are changed. Pass endFrame:null to clear it (revert to natural-length playback)."},{"name":"set_video_layer_trim","description":"Patch 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_sta"},{"name":"set_layer_block","description":"Set (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 keyf"},{"name":"set_layer_transition","description":"Set 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 l"},{"name":"move_band","description":"Place 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 "},{"name":"shift_group","description":"MOVE 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 "},{"name":"set_group_window","description":"TRIM 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: thi"},{"name":"set_duration","description":"Author an EXPLICIT composition length in seconds, pinning it (duration_authored=true) so the auto-fit no longer drives it. Morpha normally DERIVES the comp leng"},{"name":"fit_duration_to_content","description":"Clear 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-"},{"name":"cut_range","description":"Ripple-delete a time window [startFrame, endFrame) — remove that span and pull all later content earlier by delta = endFrame - startFrame (the NLE 'ripple delet"},{"name":"set_embed_origins","description":"Replace the project's embed allowlist — the hostnames permitted to load this project through the public <morpha-video> embed. Pass the full desired list; it ove"},{"name":"add_embed_origin","description":"Add 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"},{"name":"remove_embed_origin","description":"Remove 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 OF"},{"name":"set_custom_font","description":"Register 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 bui"},{"name":"list_fonts","description":"List available font families across every source the editor knows about (Google + Bunny + Fontshare + Fontsource + Velvetyne) PLUS the project's user-uploaded c"},{"name":"set_layer_text","description":"Edit 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"},{"name":"add_text_layer","description":"Create 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-lin"},{"name":"add_caption_track","description":"Build 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 "},{"name":"split_caption_line","description":"Split 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, "},{"name":"merge_caption_lines","description":"Merge 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. "},{"name":"rename_layer","description":"Set 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"},{"name":"set_loop","description":"Set 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,"},{"name":"set_canvas_size","description":"Resize 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"},{"name":"set_image_filename","description":"Repoint an existing image layer at a different uploaded asset — keeps the layer's id, position, size, animations, and styles; only the bitmap changes. The asset"},{"name":"set_video_clip","description":"Repoint 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 ch"},{"name":"set_matte_source","description":"Set (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 "},{"name":"freeze_frame","description":"Freeze 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"},{"name":"set_clip_speed","description":"Play 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 tri"},{"name":"add_speed_keyframe","description":"Add 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"},{"name":"remove_speed_keyframe","description":"Remove 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 th"},{"name":"set_video_layer_muted","description":"Mute 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 c"},{"name":"add_page","description":"Append 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 "},{"name":"delete_page","description":"Remove 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; "},{"name":"reorder_pages","description":"Move 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 b"},{"name":"select_page","description":"Switch which page is ACTIVE — the page the content tools target. Subsequent describe_video / inspect_layers / all content tools read and write this page until t"}],"toolCount":98,"toolsHash":"1d8058031c2b01dc98a73399106e95c50848d49eda2c44d35d2d4ab73e293a32","serverName":"morpha","capabilities":["tools"],"serverVersion":"1.0.375","protocolVersion":"2025-06-18"}},{"at":"2026-10-10T03:25:45.116Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":29,"error":null,"detail":{"tools":[{"name":"list_projects","description":"List 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 "},{"name":"list_workspaces","description":"List the workspaces (shared team spaces) the account belongs to, as { id, name, role, memberCount } entries. `role` is the caller's role in that workspace (owne"},{"name":"create_workspace","description":"Create 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 "},{"name":"rename_workspace","description":"Rename 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 w"},{"name":"open_project","description":"Get a tappable link that opens a project directly in the editor (the editor loads exactly this project from the URL). Returns { name, editorUrl }. Use this when"},{"name":"create_project","description":"Create a new project and return its server-minted id. The id is an opaque v4 UUID assigned automatically — you never choose or name it; refer to the project by "},{"name":"move_project_to_workspace","description":"Move 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 — ever"},{"name":"list_collection","description":"List the user's reusable Collection — layers they added with add_to_collection (lower-thirds, logo stings, brand intros, or any single layer). Returns { items: "},{"name":"add_from_collection","description":"Add 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"},{"name":"duplicate_project","description":"Duplicate 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 `proje"},{"name":"rename_project","description":"Update 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"},{"name":"reid_project","description":"Change 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), versio"},{"name":"delete_project","description":"Permanently delete a project: its JSON, shortlist, versions, uploaded assets, and uploaded clips. Requires edit access — the owner, a workspace editor/admin/own"},{"name":"save_version","description":"Save a version of the current project state. ALWAYS call this once after each meaningful set of changes you make on the user's behalf — the user flicks between "},{"name":"list_versions","description":"List every saved version of a project, newest-first. Returns summaries (id, name, timestamp, source, kind, version_number, cp) — not the inner project JSON. `ki"},{"name":"restore_version","description":"Load 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"},{"name":"rename_version","description":"Rename 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 cha"},{"name":"delete_version","description":"Permanently 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"},{"name":"upload_image","description":"Upload an image into a project by fetching a public http(s) URL server-side. The target is the URL you pass and nothing else: Morpha's Worker fetches it with a "},{"name":"upload_audio","description":"Upload an audio track into a project by fetching a public http(s) URL server-side — the ONLY way to get an audio file's bytes into a project over MCP/HTTP (add_"},{"name":"create_upload_link","description":"Put a file from the caller's own disk into a project: an image (.png/.jpg/.jpeg/.gif/.webp/.svg), an audio track (.mp3/.m4a/.wav/.ogg/.aac) or a font (.woff2/.w"},{"name":"detect_text_regions","description":"Return 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"},{"name":"safe_zones","description":"Return 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 'te"},{"name":"transcribe_clip","description":"Return 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 absen"},{"name":"clip_processing_status","description":"Report 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, aud"},{"name":"render_video","description":"Render 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', "},{"name":"render_status","description":"Check 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 a"},{"name":"describe_video","description":"Structural OVERVIEW of the composition (the table of contents) — canvas size, duration, platform_safe_area (the part of the canvas TikTok / Reels UI and Instagr"},{"name":"inspect_layers","description":"Full 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 tr"},{"name":"move_layer","description":"Set 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/he"},{"name":"set_pivot","description":"Set 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 — "},{"name":"add_keyframe","description":"Add 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 t"},{"name":"set_keyframes_batch","description":"Add 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"},{"name":"add_keyframes","description":"Add 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"},{"name":"remove_keyframe","description":"Remove 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."},{"name":"shift_track","description":"Bulk-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 "},{"name":"set_track_loop","description":"Set the extrapolation mode for one property's animation track. Modes: \"hold\" (default — holds the boundary keyframe's value past the ends), \"loop\" (wraps frames"},{"name":"add_image_layer","description":"Add an image layer. The asset must already be uploaded (the editor's drag-drop, upload_image, create_upload_link, or POST /api/upload-asset/<projectId> with the"},{"name":"add_video_layer","description":"Add a video layer. The clip must already be uploaded (the morphareels-sdk's client.addVideo, or the editor). An upload returns { filename, name }: pass `filenam"},{"name":"add_shape","description":"Add a shape layer. `kind` selects the primitive: rect | ellipse | triangle | star | pentagon | hexagon | arrow | heart | rounded-rect | diamond | parallelogram "},{"name":"add_curve","description":"Draw 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 t"},{"name":"duplicate_layer","description":"Composition primitive: clone a leaf (image.<id> / video.<id> / shapes.<id> / text.<id>) `count` times, applying a cumulative per-step transform — copy i is offs"},{"name":"remove_layer","description":"Delete 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 back"},{"name":"reorder_layer","description":"Move 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"},{"name":"set_style","description":"Set 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 — inne"},{"name":"set_layer_fill","description":"Set 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 liter"},{"name":"set_text_background","description":"Add 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 optiona"},{"name":"set_group_box","description":"Set 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 ba"},{"name":"add_color_keyframe","description":"Add 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_backg"},{"name":"remove_color_keyframe","description":"Remove 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 ent"},{"name":"fade_layer","description":"Fade a layer's opacity between two frames in one call."},{"name":"set_layer_visible","description":"Show or hide a layer instantly by writing a single opacity keyframe (1 or 0) at frame 0."},{"name":"apply_preset","description":"Apply a canned animation preset to a layer."},{"name":"apply_preset_stagger","description":"Apply 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"},{"name":"group_layers","description":"Wrap sibling elements in a new group. USE THIS whenever several layers are one thing — a butterfly assembled from wings + body + antennae, an icon built from pr"},{"name":"ungroup_layers","description":"Dissolve 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"},{"name":"set_group_parent","description":"Move an element into a group (or out to root). Refuses to place a group inside its own descendants."},{"name":"rename_group","description":"Rename a group. Pure cosmetic — labels appear in the Inspector and describe_video output."},{"name":"add_to_collection","description":"Add 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 i"},{"name":"remove_from_collection","description":"Remove 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 "},{"name":"add_morpha_layer","description":"Embed 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"},{"name":"add_audio_overlay","description":"Add an audio overlay (mp3/m4a/wav/ogg) scheduled at a frame-aligned start. The asset must already be uploaded (upload_audio, create_upload_link, or the editor);"},{"name":"remove_audio_overlay","description":"Delete an audio overlay by id."},{"name":"update_audio_overlay","description":"Patch an existing audio overlay. Only the fields you pass are changed. Pass endFrame:null to clear it (revert to natural-length playback)."},{"name":"set_video_layer_trim","description":"Patch 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_sta"},{"name":"set_layer_block","description":"Set (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 keyf"},{"name":"set_layer_transition","description":"Set 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 l"},{"name":"move_band","description":"Place 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 "},{"name":"shift_group","description":"MOVE 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 "},{"name":"set_group_window","description":"TRIM 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: thi"},{"name":"set_duration","description":"Author an EXPLICIT composition length in seconds, pinning it (duration_authored=true) so the auto-fit no longer drives it. Morpha normally DERIVES the comp leng"},{"name":"fit_duration_to_content","description":"Clear 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-"},{"name":"cut_range","description":"Ripple-delete a time window [startFrame, endFrame) — remove that span and pull all later content earlier by delta = endFrame - startFrame (the NLE 'ripple delet"},{"name":"set_embed_origins","description":"Replace the project's embed allowlist — the hostnames permitted to load this project through the public <morpha-video> embed. Pass the full desired list; it ove"},{"name":"add_embed_origin","description":"Add 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"},{"name":"remove_embed_origin","description":"Remove 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 OF"},{"name":"set_custom_font","description":"Register 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 bui"},{"name":"list_fonts","description":"List available font families across every source the editor knows about (Google + Bunny + Fontshare + Fontsource + Velvetyne) PLUS the project's user-uploaded c"},{"name":"set_layer_text","description":"Edit 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"},{"name":"add_text_layer","description":"Create 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-lin"},{"name":"add_caption_track","description":"Build 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 "},{"name":"split_caption_line","description":"Split 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, "},{"name":"merge_caption_lines","description":"Merge 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. "},{"name":"rename_layer","description":"Set 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"},{"name":"set_loop","description":"Set 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,"},{"name":"set_canvas_size","description":"Resize 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"},{"name":"set_image_filename","description":"Repoint an existing image layer at a different uploaded asset — keeps the layer's id, position, size, animations, and styles; only the bitmap changes. The asset"},{"name":"set_video_clip","description":"Repoint 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 ch"},{"name":"set_matte_source","description":"Set (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 "},{"name":"freeze_frame","description":"Freeze 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"},{"name":"set_clip_speed","description":"Play 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 tri"},{"name":"add_speed_keyframe","description":"Add 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"},{"name":"remove_speed_keyframe","description":"Remove 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 th"},{"name":"set_video_layer_muted","description":"Mute 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 c"},{"name":"add_page","description":"Append 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 "},{"name":"delete_page","description":"Remove 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; "},{"name":"reorder_pages","description":"Move 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 b"},{"name":"select_page","description":"Switch which page is ACTIVE — the page the content tools target. Subsequent describe_video / inspect_layers / all content tools read and write this page until t"}],"toolCount":98,"toolsHash":"1d8058031c2b01dc98a73399106e95c50848d49eda2c44d35d2d4ab73e293a32","serverName":"morpha","capabilities":["tools"],"serverVersion":"1.0.375","protocolVersion":"2025-06-18"}},{"at":"2026-10-09T20:29:34.939Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":55,"error":null,"detail":{"tools":[{"name":"list_projects","description":"List 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 "},{"name":"list_workspaces","description":"List the workspaces (shared team spaces) the account belongs to, as { id, name, role, memberCount } entries. `role` is the caller's role in that workspace (owne"},{"name":"create_workspace","description":"Create 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 "},{"name":"rename_workspace","description":"Rename 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 w"},{"name":"open_project","description":"Get a tappable link that opens a project directly in the editor (the editor loads exactly this project from the URL). Returns { name, editorUrl }. Use this when"},{"name":"create_project","description":"Create a new project and return its server-minted id. The id is an opaque v4 UUID assigned automatically — you never choose or name it; refer to the project by "},{"name":"move_project_to_workspace","description":"Move 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 — ever"},{"name":"list_collection","description":"List the user's reusable Collection — layers they added with add_to_collection (lower-thirds, logo stings, brand intros, or any single layer). Returns { items: "},{"name":"add_from_collection","description":"Add 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"},{"name":"duplicate_project","description":"Duplicate 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 `proje"},{"name":"rename_project","description":"Update 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"},{"name":"reid_project","description":"Change 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), versio"},{"name":"delete_project","description":"Permanently delete a project: its JSON, shortlist, versions, uploaded assets, and uploaded clips. Requires edit access — the owner, a workspace editor/admin/own"},{"name":"save_version","description":"Save a version of the current project state. ALWAYS call this once after each meaningful set of changes you make on the user's behalf — the user flicks between "},{"name":"list_versions","description":"List every saved version of a project, newest-first. Returns summaries (id, name, timestamp, source, kind, version_number, cp) — not the inner project JSON. `ki"},{"name":"restore_version","description":"Load 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"},{"name":"rename_version","description":"Rename 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 cha"},{"name":"delete_version","description":"Permanently 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"},{"name":"upload_image","description":"Upload an image into a project by fetching a public http(s) URL server-side. The target is the URL you pass and nothing else: Morpha's Worker fetches it with a "},{"name":"upload_audio","description":"Upload an audio track into a project by fetching a public http(s) URL server-side — the ONLY way to get an audio file's bytes into a project over MCP/HTTP (add_"},{"name":"create_upload_link","description":"Put a file from the caller's own disk into a project: an image (.png/.jpg/.jpeg/.gif/.webp/.svg), an audio track (.mp3/.m4a/.wav/.ogg/.aac) or a font (.woff2/.w"},{"name":"detect_text_regions","description":"Return 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"},{"name":"safe_zones","description":"Return 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 'te"},{"name":"transcribe_clip","description":"Return 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 absen"},{"name":"clip_processing_status","description":"Report 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, aud"},{"name":"render_video","description":"Render 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', "},{"name":"render_status","description":"Check 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 a"},{"name":"describe_video","description":"Structural OVERVIEW of the composition (the table of contents) — canvas size, duration, platform_safe_area (the part of the canvas TikTok / Reels UI and Instagr"},{"name":"inspect_layers","description":"Full 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 tr"},{"name":"move_layer","description":"Set 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/he"},{"name":"set_pivot","description":"Set 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 — "},{"name":"add_keyframe","description":"Add 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 t"},{"name":"set_keyframes_batch","description":"Add 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"},{"name":"add_keyframes","description":"Add 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"},{"name":"remove_keyframe","description":"Remove 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."},{"name":"shift_track","description":"Bulk-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 "},{"name":"set_track_loop","description":"Set the extrapolation mode for one property's animation track. Modes: \"hold\" (default — holds the boundary keyframe's value past the ends), \"loop\" (wraps frames"},{"name":"add_image_layer","description":"Add an image layer. The asset must already be uploaded (the editor's drag-drop, upload_image, create_upload_link, or POST /api/upload-asset/<projectId> with the"},{"name":"add_video_layer","description":"Add a video layer. The clip must already be uploaded (the morphareels-sdk's client.addVideo, or the editor). An upload returns { filename, name }: pass `filenam"},{"name":"add_shape","description":"Add a shape layer. `kind` selects the primitive: rect | ellipse | triangle | star | pentagon | hexagon | arrow | heart | rounded-rect | diamond | parallelogram "},{"name":"add_curve","description":"Draw 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 t"},{"name":"duplicate_layer","description":"Composition primitive: clone a leaf (image.<id> / video.<id> / shapes.<id> / text.<id>) `count` times, applying a cumulative per-step transform — copy i is offs"},{"name":"remove_layer","description":"Delete 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 back"},{"name":"reorder_layer","description":"Move 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"},{"name":"set_style","description":"Set 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 — inne"},{"name":"set_layer_fill","description":"Set 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 liter"},{"name":"set_text_background","description":"Add 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 optiona"},{"name":"set_group_box","description":"Set 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 ba"},{"name":"add_color_keyframe","description":"Add 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_backg"},{"name":"remove_color_keyframe","description":"Remove 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 ent"},{"name":"fade_layer","description":"Fade a layer's opacity between two frames in one call."},{"name":"set_layer_visible","description":"Show or hide a layer instantly by writing a single opacity keyframe (1 or 0) at frame 0."},{"name":"apply_preset","description":"Apply a canned animation preset to a layer."},{"name":"apply_preset_stagger","description":"Apply 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"},{"name":"group_layers","description":"Wrap sibling elements in a new group. USE THIS whenever several layers are one thing — a butterfly assembled from wings + body + antennae, an icon built from pr"},{"name":"ungroup_layers","description":"Dissolve 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"},{"name":"set_group_parent","description":"Move an element into a group (or out to root). Refuses to place a group inside its own descendants."},{"name":"rename_group","description":"Rename a group. Pure cosmetic — labels appear in the Inspector and describe_video output."},{"name":"add_to_collection","description":"Add 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 i"},{"name":"remove_from_collection","description":"Remove 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 "},{"name":"add_morpha_layer","description":"Embed 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"},{"name":"add_audio_overlay","description":"Add an audio overlay (mp3/m4a/wav/ogg) scheduled at a frame-aligned start. The asset must already be uploaded (upload_audio, create_upload_link, or the editor);"},{"name":"remove_audio_overlay","description":"Delete an audio overlay by id."},{"name":"update_audio_overlay","description":"Patch an existing audio overlay. Only the fields you pass are changed. Pass endFrame:null to clear it (revert to natural-length playback)."},{"name":"set_video_layer_trim","description":"Patch 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_sta"},{"name":"set_layer_block","description":"Set (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 keyf"},{"name":"set_layer_transition","description":"Set 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 l"},{"name":"move_band","description":"Place 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 "},{"name":"shift_group","description":"MOVE 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 "},{"name":"set_group_window","description":"TRIM 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: thi"},{"name":"set_duration","description":"Author an EXPLICIT composition length in seconds, pinning it (duration_authored=true) so the auto-fit no longer drives it. Morpha normally DERIVES the comp leng"},{"name":"fit_duration_to_content","description":"Clear 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-"},{"name":"cut_range","description":"Ripple-delete a time window [startFrame, endFrame) — remove that span and pull all later content earlier by delta = endFrame - startFrame (the NLE 'ripple delet"},{"name":"set_embed_origins","description":"Replace the project's embed allowlist — the hostnames permitted to load this project through the public <morpha-video> embed. Pass the full desired list; it ove"},{"name":"add_embed_origin","description":"Add 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"},{"name":"remove_embed_origin","description":"Remove 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 OF"},{"name":"set_custom_font","description":"Register 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 bui"},{"name":"list_fonts","description":"List available font families across every source the editor knows about (Google + Bunny + Fontshare + Fontsource + Velvetyne) PLUS the project's user-uploaded c"},{"name":"set_layer_text","description":"Edit 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"},{"name":"add_text_layer","description":"Create 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-lin"},{"name":"add_caption_track","description":"Build 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 "},{"name":"split_caption_line","description":"Split 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, "},{"name":"merge_caption_lines","description":"Merge 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. "},{"name":"rename_layer","description":"Set 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"},{"name":"set_loop","description":"Set 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,"},{"name":"set_canvas_size","description":"Resize 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"},{"name":"set_image_filename","description":"Repoint an existing image layer at a different uploaded asset — keeps the layer's id, position, size, animations, and styles; only the bitmap changes. The asset"},{"name":"set_video_clip","description":"Repoint 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 ch"},{"name":"set_matte_source","description":"Set (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 "},{"name":"freeze_frame","description":"Freeze 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"},{"name":"set_clip_speed","description":"Play 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 tri"},{"name":"add_speed_keyframe","description":"Add 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"},{"name":"remove_speed_keyframe","description":"Remove 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 th"},{"name":"set_video_layer_muted","description":"Mute 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 c"},{"name":"add_page","description":"Append 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 "},{"name":"delete_page","description":"Remove 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; "},{"name":"reorder_pages","description":"Move 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 b"},{"name":"select_page","description":"Switch which page is ACTIVE — the page the content tools target. Subsequent describe_video / inspect_layers / all content tools read and write this page until t"}],"toolCount":98,"toolsHash":"1d8058031c2b01dc98a73399106e95c50848d49eda2c44d35d2d4ab73e293a32","serverName":"morpha","capabilities":["tools"],"serverVersion":"1.0.375","protocolVersion":"2025-06-18"}},{"at":"2026-10-09T13:31:27.257Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":59,"error":null,"detail":{"tools":[{"name":"list_projects","description":"List 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 "},{"name":"list_workspaces","description":"List the workspaces (shared team spaces) the account belongs to, as { id, name, role, memberCount } entries. `role` is the caller's role in that workspace (owne"},{"name":"create_workspace","description":"Create 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 "},{"name":"rename_workspace","description":"Rename 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 w"},{"name":"open_project","description":"Get a tappable link that opens a project directly in the editor (the editor loads exactly this project from the URL). Returns { name, editorUrl }. Use this when"},{"name":"create_project","description":"Create a new project and return its server-minted id. The id is an opaque v4 UUID assigned automatically — you never choose or name it; refer to the project by "},{"name":"move_project_to_workspace","description":"Move 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 — ever"},{"name":"list_collection","description":"List the user's reusable Collection — layers they added with add_to_collection (lower-thirds, logo stings, brand intros, or any single layer). Returns { items: "},{"name":"add_from_collection","description":"Add 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"},{"name":"duplicate_project","description":"Duplicate 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 `proje"},{"name":"rename_project","description":"Update 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"},{"name":"reid_project","description":"Change 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), versio"},{"name":"delete_project","description":"Permanently delete a project: its JSON, shortlist, versions, uploaded assets, and uploaded clips. Requires edit access — the owner, a workspace editor/admin/own"},{"name":"save_version","description":"Save a version of the current project state. ALWAYS call this once after each meaningful set of changes you make on the user's behalf — the user flicks between "},{"name":"list_versions","description":"List every saved version of a project, newest-first. Returns summaries (id, name, timestamp, source, kind, version_number, cp) — not the inner project JSON. `ki"},{"name":"restore_version","description":"Load 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"},{"name":"rename_version","description":"Rename 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 cha"},{"name":"delete_version","description":"Permanently 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"},{"name":"upload_image","description":"Upload an image into a project by fetching a public http(s) URL server-side. The target is the URL you pass and nothing else: Morpha's Worker fetches it with a "},{"name":"upload_audio","description":"Upload an audio track into a project by fetching a public http(s) URL server-side — the ONLY way to get an audio file's bytes into a project over MCP/HTTP (add_"},{"name":"create_upload_link","description":"Put a file from the caller's own disk into a project: an image (.png/.jpg/.jpeg/.gif/.webp/.svg), an audio track (.mp3/.m4a/.wav/.ogg/.aac) or a font (.woff2/.w"},{"name":"detect_text_regions","description":"Return 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"},{"name":"safe_zones","description":"Return 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 'te"},{"name":"transcribe_clip","description":"Return 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 absen"},{"name":"clip_processing_status","description":"Report 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, aud"},{"name":"render_video","description":"Render 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', "},{"name":"render_status","description":"Check 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 a"},{"name":"describe_video","description":"Structural OVERVIEW of the composition (the table of contents) — canvas size, duration, platform_safe_area (the part of the canvas TikTok / Reels UI and Instagr"},{"name":"inspect_layers","description":"Full 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 tr"},{"name":"move_layer","description":"Set 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/he"},{"name":"set_pivot","description":"Set 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 — "},{"name":"add_keyframe","description":"Add 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 t"},{"name":"set_keyframes_batch","description":"Add 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"},{"name":"add_keyframes","description":"Add 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"},{"name":"remove_keyframe","description":"Remove 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."},{"name":"shift_track","description":"Bulk-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 "},{"name":"set_track_loop","description":"Set the extrapolation mode for one property's animation track. Modes: \"hold\" (default — holds the boundary keyframe's value past the ends), \"loop\" (wraps frames"},{"name":"add_image_layer","description":"Add an image layer. The asset must already be uploaded (the editor's drag-drop, upload_image, create_upload_link, or POST /api/upload-asset/<projectId> with the"},{"name":"add_video_layer","description":"Add a video layer. The clip must already be uploaded (the morphareels-sdk's client.addVideo, or the editor). An upload returns { filename, name }: pass `filenam"},{"name":"add_shape","description":"Add a shape layer. `kind` selects the primitive: rect | ellipse | triangle | star | pentagon | hexagon | arrow | heart | rounded-rect | diamond | parallelogram "},{"name":"add_curve","description":"Draw 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 t"},{"name":"duplicate_layer","description":"Composition primitive: clone a leaf (image.<id> / video.<id> / shapes.<id> / text.<id>) `count` times, applying a cumulative per-step transform — copy i is offs"},{"name":"remove_layer","description":"Delete 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 back"},{"name":"reorder_layer","description":"Move 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"},{"name":"set_style","description":"Set 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 — inne"},{"name":"set_layer_fill","description":"Set 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 liter"},{"name":"set_text_background","description":"Add 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 optiona"},{"name":"set_group_box","description":"Set 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 ba"},{"name":"add_color_keyframe","description":"Add 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_backg"},{"name":"remove_color_keyframe","description":"Remove 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 ent"},{"name":"fade_layer","description":"Fade a layer's opacity between two frames in one call."},{"name":"set_layer_visible","description":"Show or hide a layer instantly by writing a single opacity keyframe (1 or 0) at frame 0."},{"name":"apply_preset","description":"Apply a canned animation preset to a layer."},{"name":"apply_preset_stagger","description":"Apply 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"},{"name":"group_layers","description":"Wrap sibling elements in a new group. USE THIS whenever several layers are one thing — a butterfly assembled from wings + body + antennae, an icon built from pr"},{"name":"ungroup_layers","description":"Dissolve 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"},{"name":"set_group_parent","description":"Move an element into a group (or out to root). Refuses to place a group inside its own descendants."},{"name":"rename_group","description":"Rename a group. Pure cosmetic — labels appear in the Inspector and describe_video output."},{"name":"add_to_collection","description":"Add 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 i"},{"name":"remove_from_collection","description":"Remove 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 "},{"name":"add_morpha_layer","description":"Embed 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"},{"name":"add_audio_overlay","description":"Add an audio overlay (mp3/m4a/wav/ogg) scheduled at a frame-aligned start. The asset must already be uploaded (upload_audio, create_upload_link, or the editor);"},{"name":"remove_audio_overlay","description":"Delete an audio overlay by id."},{"name":"update_audio_overlay","description":"Patch an existing audio overlay. Only the fields you pass are changed. Pass endFrame:null to clear it (revert to natural-length playback)."},{"name":"set_video_layer_trim","description":"Patch 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_sta"},{"name":"set_layer_block","description":"Set (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 keyf"},{"name":"set_layer_transition","description":"Set 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 l"},{"name":"move_band","description":"Place 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 "},{"name":"shift_group","description":"MOVE 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 "},{"name":"set_group_window","description":"TRIM 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: thi"},{"name":"set_duration","description":"Author an EXPLICIT composition length in seconds, pinning it (duration_authored=true) so the auto-fit no longer drives it. Morpha normally DERIVES the comp leng"},{"name":"fit_duration_to_content","description":"Clear 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-"},{"name":"cut_range","description":"Ripple-delete a time window [startFrame, endFrame) — remove that span and pull all later content earlier by delta = endFrame - startFrame (the NLE 'ripple delet"},{"name":"set_embed_origins","description":"Replace the project's embed allowlist — the hostnames permitted to load this project through the public <morpha-video> embed. Pass the full desired list; it ove"},{"name":"add_embed_origin","description":"Add 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"},{"name":"remove_embed_origin","description":"Remove 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 OF"},{"name":"set_custom_font","description":"Register 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 bui"},{"name":"list_fonts","description":"List available font families across every source the editor knows about (Google + Bunny + Fontshare + Fontsource + Velvetyne) PLUS the project's user-uploaded c"},{"name":"set_layer_text","description":"Edit 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"},{"name":"add_text_layer","description":"Create 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-lin"},{"name":"add_caption_track","description":"Build 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 "},{"name":"split_caption_line","description":"Split 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, "},{"name":"merge_caption_lines","description":"Merge 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. "},{"name":"rename_layer","description":"Set 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"},{"name":"set_loop","description":"Set 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,"},{"name":"set_canvas_size","description":"Resize 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"},{"name":"set_image_filename","description":"Repoint an existing image layer at a different uploaded asset — keeps the layer's id, position, size, animations, and styles; only the bitmap changes. The asset"},{"name":"set_video_clip","description":"Repoint 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 ch"},{"name":"set_matte_source","description":"Set (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 "},{"name":"freeze_frame","description":"Freeze 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"},{"name":"set_clip_speed","description":"Play 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 tri"},{"name":"add_speed_keyframe","description":"Add 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"},{"name":"remove_speed_keyframe","description":"Remove 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 th"},{"name":"set_video_layer_muted","description":"Mute 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 c"},{"name":"add_page","description":"Append 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 "},{"name":"delete_page","description":"Remove 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; "},{"name":"reorder_pages","description":"Move 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 b"},{"name":"select_page","description":"Switch which page is ACTIVE — the page the content tools target. Subsequent describe_video / inspect_layers / all content tools read and write this page until t"}],"toolCount":98,"toolsHash":"1d8058031c2b01dc98a73399106e95c50848d49eda2c44d35d2d4ab73e293a32","serverName":"morpha","capabilities":["tools"],"serverVersion":"1.0.375","protocolVersion":"2025-06-18"}},{"at":"2026-10-09T07:26:37.059Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":47,"error":null,"detail":{"tools":[{"name":"list_projects","description":"List 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 "},{"name":"list_workspaces","description":"List the workspaces (shared team spaces) the account belongs to, as { id, name, role, memberCount } entries. `role` is the caller's role in that workspace (owne"},{"name":"create_workspace","description":"Create 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 "},{"name":"rename_workspace","description":"Rename 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 w"},{"name":"open_project","description":"Get a tappable link that opens a project directly in the editor (the editor loads exactly this project from the URL). Returns { name, editorUrl }. Use this when"},{"name":"create_project","description":"Create a new project and return its server-minted id. The id is an opaque v4 UUID assigned automatically — you never choose or name it; refer to the project by "},{"name":"move_project_to_workspace","description":"Move 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 — ever"},{"name":"list_collection","description":"List the user's reusable Collection — layers they added with add_to_collection (lower-thirds, logo stings, brand intros, or any single layer). Returns { items: "},{"name":"add_from_collection","description":"Add 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"},{"name":"duplicate_project","description":"Duplicate 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 `proje"},{"name":"rename_project","description":"Update 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"},{"name":"reid_project","description":"Change 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), versio"},{"name":"delete_project","description":"Permanently delete a project: its JSON, shortlist, versions, uploaded assets, and uploaded clips. Requires edit access — the owner, a workspace editor/admin/own"},{"name":"save_version","description":"Save a version of the current project state. ALWAYS call this once after each meaningful set of changes you make on the user's behalf — the user flicks between "},{"name":"list_versions","description":"List every saved version of a project, newest-first. Returns summaries (id, name, timestamp, source, kind, version_number, cp) — not the inner project JSON. `ki"},{"name":"restore_version","description":"Load 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"},{"name":"rename_version","description":"Rename 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 cha"},{"name":"delete_version","description":"Permanently 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"},{"name":"upload_image","description":"Upload an image into a project by fetching a public http(s) URL server-side. The target is the URL you pass and nothing else: Morpha's Worker fetches it with a "},{"name":"upload_audio","description":"Upload an audio track into a project by fetching a public http(s) URL server-side — the ONLY way to get an audio file's bytes into a project over MCP/HTTP (add_"},{"name":"create_upload_link","description":"Put a file from the caller's own disk into a project: an image (.png/.jpg/.jpeg/.gif/.webp/.svg), an audio track (.mp3/.m4a/.wav/.ogg/.aac) or a font (.woff2/.w"},{"name":"detect_text_regions","description":"Return 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"},{"name":"safe_zones","description":"Return 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 'te"},{"name":"transcribe_clip","description":"Return 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 absen"},{"name":"clip_processing_status","description":"Report 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, aud"},{"name":"render_video","description":"Render 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', "},{"name":"render_status","description":"Check 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 a"},{"name":"describe_video","description":"Structural OVERVIEW of the composition (the table of contents) — canvas size, duration, platform_safe_area (the part of the canvas TikTok / Reels UI and Instagr"},{"name":"inspect_layers","description":"Full 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 tr"},{"name":"move_layer","description":"Set 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/he"},{"name":"set_pivot","description":"Set 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 — "},{"name":"add_keyframe","description":"Add 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 t"},{"name":"set_keyframes_batch","description":"Add 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"},{"name":"add_keyframes","description":"Add 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"},{"name":"remove_keyframe","description":"Remove 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."},{"name":"shift_track","description":"Bulk-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 "},{"name":"set_track_loop","description":"Set the extrapolation mode for one property's animation track. Modes: \"hold\" (default — holds the boundary keyframe's value past the ends), \"loop\" (wraps frames"},{"name":"add_image_layer","description":"Add an image layer. The asset must already be uploaded (the editor's drag-drop, upload_image, create_upload_link, or POST /api/upload-asset/<projectId> with the"},{"name":"add_video_layer","description":"Add a video layer. The clip must already be uploaded (the morphareels-sdk's client.addVideo, or the editor). An upload returns { filename, name }: pass `filenam"},{"name":"add_shape","description":"Add a shape layer. `kind` selects the primitive: rect | ellipse | triangle | star | pentagon | hexagon | arrow | heart | rounded-rect | diamond | parallelogram "},{"name":"add_curve","description":"Draw 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 t"},{"name":"duplicate_layer","description":"Composition primitive: clone a leaf (image.<id> / video.<id> / shapes.<id> / text.<id>) `count` times, applying a cumulative per-step transform — copy i is offs"},{"name":"remove_layer","description":"Delete 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 back"},{"name":"reorder_layer","description":"Move 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"},{"name":"set_style","description":"Set 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 — inne"},{"name":"set_layer_fill","description":"Set 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 liter"},{"name":"set_text_background","description":"Add 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 optiona"},{"name":"set_group_box","description":"Set 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 ba"},{"name":"add_color_keyframe","description":"Add 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_backg"},{"name":"remove_color_keyframe","description":"Remove 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 ent"},{"name":"fade_layer","description":"Fade a layer's opacity between two frames in one call."},{"name":"set_layer_visible","description":"Show or hide a layer instantly by writing a single opacity keyframe (1 or 0) at frame 0."},{"name":"apply_preset","description":"Apply a canned animation preset to a layer."},{"name":"apply_preset_stagger","description":"Apply 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"},{"name":"group_layers","description":"Wrap sibling elements in a new group. USE THIS whenever several layers are one thing — a butterfly assembled from wings + body + antennae, an icon built from pr"},{"name":"ungroup_layers","description":"Dissolve 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"},{"name":"set_group_parent","description":"Move an element into a group (or out to root). Refuses to place a group inside its own descendants."},{"name":"rename_group","description":"Rename a group. Pure cosmetic — labels appear in the Inspector and describe_video output."},{"name":"add_to_collection","description":"Add 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 i"},{"name":"remove_from_collection","description":"Remove 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 "},{"name":"add_morpha_layer","description":"Embed 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"},{"name":"add_audio_overlay","description":"Add an audio overlay (mp3/m4a/wav/ogg) scheduled at a frame-aligned start. The asset must already be uploaded (upload_audio, create_upload_link, or the editor);"},{"name":"remove_audio_overlay","description":"Delete an audio overlay by id."},{"name":"update_audio_overlay","description":"Patch an existing audio overlay. Only the fields you pass are changed. Pass endFrame:null to clear it (revert to natural-length playback)."},{"name":"set_video_layer_trim","description":"Patch 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_sta"},{"name":"set_layer_block","description":"Set (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 keyf"},{"name":"set_layer_transition","description":"Set 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 l"},{"name":"move_band","description":"Place 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 "},{"name":"shift_group","description":"MOVE 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 "},{"name":"set_group_window","description":"TRIM 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: thi"},{"name":"set_duration","description":"Author an EXPLICIT composition length in seconds, pinning it (duration_authored=true) so the auto-fit no longer drives it. Morpha normally DERIVES the comp leng"},{"name":"fit_duration_to_content","description":"Clear 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-"},{"name":"cut_range","description":"Ripple-delete a time window [startFrame, endFrame) — remove that span and pull all later content earlier by delta = endFrame - startFrame (the NLE 'ripple delet"},{"name":"set_embed_origins","description":"Replace the project's embed allowlist — the hostnames permitted to load this project through the public <morpha-video> embed. Pass the full desired list; it ove"},{"name":"add_embed_origin","description":"Add 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"},{"name":"remove_embed_origin","description":"Remove 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 OF"},{"name":"set_custom_font","description":"Register 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 bui"},{"name":"list_fonts","description":"List available font families across every source the editor knows about (Google + Bunny + Fontshare + Fontsource + Velvetyne) PLUS the project's user-uploaded c"},{"name":"set_layer_text","description":"Edit 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"},{"name":"add_text_layer","description":"Create 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-lin"},{"name":"add_caption_track","description":"Build 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 "},{"name":"split_caption_line","description":"Split 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, "},{"name":"merge_caption_lines","description":"Merge 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. "},{"name":"rename_layer","description":"Set 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"},{"name":"set_loop","description":"Set 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,"},{"name":"set_canvas_size","description":"Resize 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"},{"name":"set_image_filename","description":"Repoint an existing image layer at a different uploaded asset — keeps the layer's id, position, size, animations, and styles; only the bitmap changes. The asset"},{"name":"set_video_clip","description":"Repoint 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 ch"},{"name":"set_matte_source","description":"Set (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 "},{"name":"freeze_frame","description":"Freeze 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"},{"name":"set_clip_speed","description":"Play 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 tri"},{"name":"add_speed_keyframe","description":"Add 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"},{"name":"remove_speed_keyframe","description":"Remove 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 th"},{"name":"set_video_layer_muted","description":"Mute 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 c"},{"name":"add_page","description":"Append 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 "},{"name":"delete_page","description":"Remove 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; "},{"name":"reorder_pages","description":"Move 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 b"},{"name":"select_page","description":"Switch which page is ACTIVE — the page the content tools target. Subsequent describe_video / inspect_layers / all content tools read and write this page until t"}],"toolCount":98,"toolsHash":"1d8058031c2b01dc98a73399106e95c50848d49eda2c44d35d2d4ab73e293a32","serverName":"morpha","capabilities":["tools"],"serverVersion":"1.0.375","protocolVersion":"2025-06-18"}},{"at":"2026-10-09T01:25:01.856Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":19,"error":null,"detail":{"tools":[{"name":"list_projects","description":"List 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 "},{"name":"list_workspaces","description":"List the workspaces (shared team spaces) the account belongs to, as { id, name, role, memberCount } entries. `role` is the caller's role in that workspace (owne"},{"name":"create_workspace","description":"Create 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 "},{"name":"rename_workspace","description":"Rename 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 w"},{"name":"open_project","description":"Get a tappable link that opens a project directly in the editor (the editor loads exactly this project from the URL). Returns { name, editorUrl }. Use this when"},{"name":"create_project","description":"Create a new project and return its server-minted id. The id is an opaque v4 UUID assigned automatically — you never choose or name it; refer to the project by "},{"name":"move_project_to_workspace","description":"Move 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 — ever"},{"name":"list_collection","description":"List the user's reusable Collection — layers they added with add_to_collection (lower-thirds, logo stings, brand intros, or any single layer). Returns { items: "},{"name":"add_from_collection","description":"Add 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"},{"name":"duplicate_project","description":"Duplicate 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 `proje"},{"name":"rename_project","description":"Update 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"},{"name":"reid_project","description":"Change 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), versio"},{"name":"delete_project","description":"Permanently delete a project: its JSON, shortlist, versions, uploaded assets, and uploaded clips. Requires edit access — the owner, a workspace editor/admin/own"},{"name":"save_version","description":"Save a version of the current project state. ALWAYS call this once after each meaningful set of changes you make on the user's behalf — the user flicks between "},{"name":"list_versions","description":"List every saved version of a project, newest-first. Returns summaries (id, name, timestamp, source, kind, version_number, cp) — not the inner project JSON. `ki"},{"name":"restore_version","description":"Load 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"},{"name":"rename_version","description":"Rename 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 cha"},{"name":"delete_version","description":"Permanently 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"},{"name":"upload_image","description":"Upload an image into a project by fetching a public http(s) URL server-side. The target is the URL you pass and nothing else: Morpha's Worker fetches it with a "},{"name":"upload_audio","description":"Upload an audio track into a project by fetching a public http(s) URL server-side — the ONLY way to get an audio file's bytes into a project over MCP/HTTP (add_"},{"name":"create_upload_link","description":"Put a file from the caller's own disk into a project: an image (.png/.jpg/.jpeg/.gif/.webp/.svg), an audio track (.mp3/.m4a/.wav/.ogg/.aac) or a font (.woff2/.w"},{"name":"detect_text_regions","description":"Return 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"},{"name":"safe_zones","description":"Return 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 'te"},{"name":"transcribe_clip","description":"Return 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 absen"},{"name":"clip_processing_status","description":"Report 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, aud"},{"name":"render_video","description":"Render 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', "},{"name":"render_status","description":"Check 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 a"},{"name":"describe_video","description":"Structural OVERVIEW of the composition (the table of contents) — canvas size, duration, platform_safe_area (the part of the canvas TikTok / Reels UI and Instagr"},{"name":"inspect_layers","description":"Full 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 tr"},{"name":"move_layer","description":"Set 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/he"},{"name":"set_pivot","description":"Set 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 — "},{"name":"add_keyframe","description":"Add 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 t"},{"name":"set_keyframes_batch","description":"Add 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"},{"name":"add_keyframes","description":"Add 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"},{"name":"remove_keyframe","description":"Remove 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."},{"name":"shift_track","description":"Bulk-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 "},{"name":"set_track_loop","description":"Set the extrapolation mode for one property's animation track. Modes: \"hold\" (default — holds the boundary keyframe's value past the ends), \"loop\" (wraps frames"},{"name":"add_image_layer","description":"Add an image layer. The asset must already be uploaded (the editor's drag-drop, upload_image, create_upload_link, or POST /api/upload-asset/<projectId> with the"},{"name":"add_video_layer","description":"Add a video layer. The clip must already be uploaded (the morphareels-sdk's client.addVideo, or the editor). An upload returns { filename, name }: pass `filenam"},{"name":"add_shape","description":"Add a shape layer. `kind` selects the primitive: rect | ellipse | triangle | star | pentagon | hexagon | arrow | heart | rounded-rect | diamond | parallelogram "},{"name":"add_curve","description":"Draw 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 t"},{"name":"duplicate_layer","description":"Composition primitive: clone a leaf (image.<id> / video.<id> / shapes.<id> / text.<id>) `count` times, applying a cumulative per-step transform — copy i is offs"},{"name":"remove_layer","description":"Delete 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 back"},{"name":"reorder_layer","description":"Move 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"},{"name":"set_style","description":"Set 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 — inne"},{"name":"set_layer_fill","description":"Set 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 liter"},{"name":"set_text_background","description":"Add 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 optiona"},{"name":"set_group_box","description":"Set 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 ba"},{"name":"add_color_keyframe","description":"Add 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_backg"},{"name":"remove_color_keyframe","description":"Remove 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 ent"},{"name":"fade_layer","description":"Fade a layer's opacity between two frames in one call."},{"name":"set_layer_visible","description":"Show or hide a layer instantly by writing a single opacity keyframe (1 or 0) at frame 0."},{"name":"apply_preset","description":"Apply a canned animation preset to a layer."},{"name":"apply_preset_stagger","description":"Apply 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"},{"name":"group_layers","description":"Wrap sibling elements in a new group. USE THIS whenever several layers are one thing — a butterfly assembled from wings + body + antennae, an icon built from pr"},{"name":"ungroup_layers","description":"Dissolve 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"},{"name":"set_group_parent","description":"Move an element into a group (or out to root). Refuses to place a group inside its own descendants."},{"name":"rename_group","description":"Rename a group. Pure cosmetic — labels appear in the Inspector and describe_video output."},{"name":"add_to_collection","description":"Add 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 i"},{"name":"remove_from_collection","description":"Remove 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 "},{"name":"add_morpha_layer","description":"Embed 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"},{"name":"add_audio_overlay","description":"Add an audio overlay (mp3/m4a/wav/ogg) scheduled at a frame-aligned start. The asset must already be uploaded (upload_audio, create_upload_link, or the editor);"},{"name":"remove_audio_overlay","description":"Delete an audio overlay by id."},{"name":"update_audio_overlay","description":"Patch an existing audio overlay. Only the fields you pass are changed. Pass endFrame:null to clear it (revert to natural-length playback)."},{"name":"set_video_layer_trim","description":"Patch 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_sta"},{"name":"set_layer_block","description":"Set (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 keyf"},{"name":"set_layer_transition","description":"Set 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 l"},{"name":"move_band","description":"Place 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 "},{"name":"shift_group","description":"MOVE 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 "},{"name":"set_group_window","description":"TRIM 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: thi"},{"name":"set_duration","description":"Author an EXPLICIT composition length in seconds, pinning it (duration_authored=true) so the auto-fit no longer drives it. Morpha normally DERIVES the comp leng"},{"name":"fit_duration_to_content","description":"Clear 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-"},{"name":"cut_range","description":"Ripple-delete a time window [startFrame, endFrame) — remove that span and pull all later content earlier by delta = endFrame - startFrame (the NLE 'ripple delet"},{"name":"set_embed_origins","description":"Replace the project's embed allowlist — the hostnames permitted to load this project through the public <morpha-video> embed. Pass the full desired list; it ove"},{"name":"add_embed_origin","description":"Add 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"},{"name":"remove_embed_origin","description":"Remove 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 OF"},{"name":"set_custom_font","description":"Register 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 bui"},{"name":"list_fonts","description":"List available font families across every source the editor knows about (Google + Bunny + Fontshare + Fontsource + Velvetyne) PLUS the project's user-uploaded c"},{"name":"set_layer_text","description":"Edit 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"},{"name":"add_text_layer","description":"Create 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-lin"},{"name":"add_caption_track","description":"Build 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 "},{"name":"split_caption_line","description":"Split 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, "},{"name":"merge_caption_lines","description":"Merge 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. "},{"name":"rename_layer","description":"Set 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"},{"name":"set_loop","description":"Set 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,"},{"name":"set_canvas_size","description":"Resize 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"},{"name":"set_image_filename","description":"Repoint an existing image layer at a different uploaded asset — keeps the layer's id, position, size, animations, and styles; only the bitmap changes. The asset"},{"name":"set_video_clip","description":"Repoint 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 ch"},{"name":"set_matte_source","description":"Set (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 "},{"name":"freeze_frame","description":"Freeze 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"},{"name":"set_clip_speed","description":"Play 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 tri"},{"name":"add_speed_keyframe","description":"Add 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"},{"name":"remove_speed_keyframe","description":"Remove 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 th"},{"name":"set_video_layer_muted","description":"Mute 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 c"},{"name":"add_page","description":"Append 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 "},{"name":"delete_page","description":"Remove 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; "},{"name":"reorder_pages","description":"Move 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 b"},{"name":"select_page","description":"Switch which page is ACTIVE — the page the content tools target. Subsequent describe_video / inspect_layers / all content tools read and write this page until t"}],"toolCount":98,"toolsHash":"1d8058031c2b01dc98a73399106e95c50848d49eda2c44d35d2d4ab73e293a32","serverName":"morpha","capabilities":["tools"],"serverVersion":"1.0.375","protocolVersion":"2025-06-18"}},{"at":"2026-10-08T18:26:53.753Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":200,"error":null,"detail":{"tools":[{"name":"list_projects","description":"List 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 "},{"name":"list_workspaces","description":"List the workspaces (shared team spaces) the account belongs to, as { id, name, role, memberCount } entries. `role` is the caller's role in that workspace (owne"},{"name":"create_workspace","description":"Create 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 "},{"name":"rename_workspace","description":"Rename 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 w"},{"name":"open_project","description":"Get a tappable link that opens a project directly in the editor (the editor loads exactly this project from the URL). Returns { name, editorUrl }. Use this when"},{"name":"create_project","description":"Create a new project and return its server-minted id. The id is an opaque v4 UUID assigned automatically — you never choose or name it; refer to the project by "},{"name":"move_project_to_workspace","description":"Move 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 — ever"},{"name":"list_collection","description":"List the user's reusable Collection — layers they added with add_to_collection (lower-thirds, logo stings, brand intros, or any single layer). Returns { items: "},{"name":"add_from_collection","description":"Add 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"},{"name":"duplicate_project","description":"Duplicate 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 `proje"},{"name":"rename_project","description":"Update 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"},{"name":"reid_project","description":"Change 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), versio"},{"name":"delete_project","description":"Permanently delete a project: its JSON, shortlist, versions, uploaded assets, and uploaded clips. Requires edit access — the owner, a workspace editor/admin/own"},{"name":"save_version","description":"Save a version of the current project state. ALWAYS call this once after each meaningful set of changes you make on the user's behalf — the user flicks between "},{"name":"list_versions","description":"List every saved version of a project, newest-first. Returns summaries (id, name, timestamp, source, kind, version_number, cp) — not the inner project JSON. `ki"},{"name":"restore_version","description":"Load 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"},{"name":"rename_version","description":"Rename 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 cha"},{"name":"delete_version","description":"Permanently 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"},{"name":"upload_image","description":"Upload an image into a project by fetching a public http(s) URL server-side. The target is the URL you pass and nothing else: Morpha's Worker fetches it with a "},{"name":"upload_audio","description":"Upload an audio track into a project by fetching a public http(s) URL server-side — the ONLY way to get an audio file's bytes into a project over MCP/HTTP (add_"},{"name":"create_upload_link","description":"Put a file from the caller's own disk into a project: an image (.png/.jpg/.jpeg/.gif/.webp/.svg), an audio track (.mp3/.m4a/.wav/.ogg/.aac) or a font (.woff2/.w"},{"name":"detect_text_regions","description":"Return 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"},{"name":"safe_zones","description":"Return 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 'te"},{"name":"transcribe_clip","description":"Return 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 absen"},{"name":"clip_processing_status","description":"Report 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, aud"},{"name":"render_video","description":"Render 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', "},{"name":"render_status","description":"Check 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 a"},{"name":"describe_video","description":"Structural OVERVIEW of the composition (the table of contents) — canvas size, duration, platform_safe_area (the part of the canvas TikTok / Reels UI and Instagr"},{"name":"inspect_layers","description":"Full 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 tr"},{"name":"move_layer","description":"Set 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/he"},{"name":"set_pivot","description":"Set 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 — "},{"name":"add_keyframe","description":"Add 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 t"},{"name":"set_keyframes_batch","description":"Add 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"},{"name":"add_keyframes","description":"Add 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"},{"name":"remove_keyframe","description":"Remove 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."},{"name":"shift_track","description":"Bulk-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 "},{"name":"set_track_loop","description":"Set the extrapolation mode for one property's animation track. Modes: \"hold\" (default — holds the boundary keyframe's value past the ends), \"loop\" (wraps frames"},{"name":"add_image_layer","description":"Add an image layer. The asset must already be uploaded (the editor's drag-drop, upload_image, create_upload_link, or POST /api/upload-asset/<projectId> with the"},{"name":"add_video_layer","description":"Add a video layer. The clip must already be uploaded (the morphareels-sdk's client.addVideo, or the editor). An upload returns { filename, name }: pass `filenam"},{"name":"add_shape","description":"Add a shape layer. `kind` selects the primitive: rect | ellipse | triangle | star | pentagon | hexagon | arrow | heart | rounded-rect | diamond | parallelogram "},{"name":"add_curve","description":"Draw 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 t"},{"name":"duplicate_layer","description":"Composition primitive: clone a leaf (image.<id> / video.<id> / shapes.<id> / text.<id>) `count` times, applying a cumulative per-step transform — copy i is offs"},{"name":"remove_layer","description":"Delete 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 back"},{"name":"reorder_layer","description":"Move 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"},{"name":"set_style","description":"Set 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 — inne"},{"name":"set_layer_fill","description":"Set 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 liter"},{"name":"set_text_background","description":"Add 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 optiona"},{"name":"set_group_box","description":"Set 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 ba"},{"name":"add_color_keyframe","description":"Add 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_backg"},{"name":"remove_color_keyframe","description":"Remove 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 ent"},{"name":"fade_layer","description":"Fade a layer's opacity between two frames in one call."},{"name":"set_layer_visible","description":"Show or hide a layer instantly by writing a single opacity keyframe (1 or 0) at frame 0."},{"name":"apply_preset","description":"Apply a canned animation preset to a layer."},{"name":"apply_preset_stagger","description":"Apply 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"},{"name":"group_layers","description":"Wrap sibling elements in a new group. USE THIS whenever several layers are one thing — a butterfly assembled from wings + body + antennae, an icon built from pr"},{"name":"ungroup_layers","description":"Dissolve 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"},{"name":"set_group_parent","description":"Move an element into a group (or out to root). Refuses to place a group inside its own descendants."},{"name":"rename_group","description":"Rename a group. Pure cosmetic — labels appear in the Inspector and describe_video output."},{"name":"add_to_collection","description":"Add 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 i"},{"name":"remove_from_collection","description":"Remove 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 "},{"name":"add_morpha_layer","description":"Embed 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"},{"name":"add_audio_overlay","description":"Add an audio overlay (mp3/m4a/wav/ogg) scheduled at a frame-aligned start. The asset must already be uploaded (upload_audio, create_upload_link, or the editor);"},{"name":"remove_audio_overlay","description":"Delete an audio overlay by id."},{"name":"update_audio_overlay","description":"Patch an existing audio overlay. Only the fields you pass are changed. Pass endFrame:null to clear it (revert to natural-length playback)."},{"name":"set_video_layer_trim","description":"Patch 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_sta"},{"name":"set_layer_block","description":"Set (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 keyf"},{"name":"set_layer_transition","description":"Set 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 l"},{"name":"move_band","description":"Place 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 "},{"name":"shift_group","description":"MOVE 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 "},{"name":"set_group_window","description":"TRIM 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: thi"},{"name":"set_duration","description":"Author an EXPLICIT composition length in seconds, pinning it (duration_authored=true) so the auto-fit no longer drives it. Morpha normally DERIVES the comp leng"},{"name":"fit_duration_to_content","description":"Clear 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-"},{"name":"cut_range","description":"Ripple-delete a time window [startFrame, endFrame) — remove that span and pull all later content earlier by delta = endFrame - startFrame (the NLE 'ripple delet"},{"name":"set_embed_origins","description":"Replace the project's embed allowlist — the hostnames permitted to load this project through the public <morpha-video> embed. Pass the full desired list; it ove"},{"name":"add_embed_origin","description":"Add 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"},{"name":"remove_embed_origin","description":"Remove 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 OF"},{"name":"set_custom_font","description":"Register 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 bui"},{"name":"list_fonts","description":"List available font families across every source the editor knows about (Google + Bunny + Fontshare + Fontsource + Velvetyne) PLUS the project's user-uploaded c"},{"name":"set_layer_text","description":"Edit 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"},{"name":"add_text_layer","description":"Create 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-lin"},{"name":"add_caption_track","description":"Build 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 "},{"name":"split_caption_line","description":"Split 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, "},{"name":"merge_caption_lines","description":"Merge 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. "},{"name":"rename_layer","description":"Set 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"},{"name":"set_loop","description":"Set 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,"},{"name":"set_canvas_size","description":"Resize 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"},{"name":"set_image_filename","description":"Repoint an existing image layer at a different uploaded asset — keeps the layer's id, position, size, animations, and styles; only the bitmap changes. The asset"},{"name":"set_video_clip","description":"Repoint 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 ch"},{"name":"set_matte_source","description":"Set (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 "},{"name":"freeze_frame","description":"Freeze 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"},{"name":"set_clip_speed","description":"Play 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 tri"},{"name":"add_speed_keyframe","description":"Add 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"},{"name":"remove_speed_keyframe","description":"Remove 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 th"},{"name":"set_video_layer_muted","description":"Mute 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 c"},{"name":"add_page","description":"Append 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 "},{"name":"delete_page","description":"Remove 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; "},{"name":"reorder_pages","description":"Move 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 b"},{"name":"select_page","description":"Switch which page is ACTIVE — the page the content tools target. Subsequent describe_video / inspect_layers / all content tools read and write this page until t"}],"toolCount":98,"toolsHash":"1d8058031c2b01dc98a73399106e95c50848d49eda2c44d35d2d4ab73e293a32","serverName":"morpha","capabilities":["tools"],"serverVersion":"1.0.375","protocolVersion":"2025-06-18"}},{"at":"2026-10-08T11:23:46.669Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":55,"error":null,"detail":{"tools":[{"name":"list_projects","description":"List 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 "},{"name":"list_workspaces","description":"List the workspaces (shared team spaces) the account belongs to, as { id, name, role, memberCount } entries. `role` is the caller's role in that workspace (owne"},{"name":"create_workspace","description":"Create 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 "},{"name":"rename_workspace","description":"Rename 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 w"},{"name":"open_project","description":"Get a tappable link that opens a project directly in the editor (the editor loads exactly this project from the URL). Returns { name, editorUrl }. Use this when"},{"name":"create_project","description":"Create a new project and return its server-minted id. The id is an opaque v4 UUID assigned automatically — you never choose or name it; refer to the project by "},{"name":"move_project_to_workspace","description":"Move 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 — ever"},{"name":"list_collection","description":"List the user's reusable Collection — layers they added with add_to_collection (lower-thirds, logo stings, brand intros, or any single layer). Returns { items: "},{"name":"add_from_collection","description":"Add 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"},{"name":"duplicate_project","description":"Duplicate 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 `proje"},{"name":"rename_project","description":"Update 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"},{"name":"reid_project","description":"Change 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), versio"},{"name":"delete_project","description":"Permanently delete a project: its JSON, shortlist, versions, uploaded assets, and uploaded clips. Requires edit access — the owner, a workspace editor/admin/own"},{"name":"save_version","description":"Save a version of the current project state. ALWAYS call this once after each meaningful set of changes you make on the user's behalf — the user flicks between "},{"name":"list_versions","description":"List every saved version of a project, newest-first. Returns summaries (id, name, timestamp, source, kind, version_number, cp) — not the inner project JSON. `ki"},{"name":"restore_version","description":"Load 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"},{"name":"rename_version","description":"Rename 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 cha"},{"name":"delete_version","description":"Permanently 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"},{"name":"upload_image","description":"Upload an image into a project by fetching a public http(s) URL server-side. The target is the URL you pass and nothing else: Morpha's Worker fetches it with a "},{"name":"upload_audio","description":"Upload an audio track into a project by fetching a public http(s) URL server-side — the ONLY way to get an audio file's bytes into a project over MCP/HTTP (add_"},{"name":"create_upload_link","description":"Put a file from the caller's own disk into a project: an image (.png/.jpg/.jpeg/.gif/.webp/.svg), an audio track (.mp3/.m4a/.wav/.ogg/.aac) or a font (.woff2/.w"},{"name":"detect_text_regions","description":"Return 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"},{"name":"safe_zones","description":"Return 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 'te"},{"name":"transcribe_clip","description":"Return 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 absen"},{"name":"clip_processing_status","description":"Report 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, aud"},{"name":"render_video","description":"Render 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', "},{"name":"render_status","description":"Check 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 a"},{"name":"describe_video","description":"Structural OVERVIEW of the composition (the table of contents) — canvas size, duration, platform_safe_area (the part of the canvas TikTok / Reels UI and Instagr"},{"name":"inspect_layers","description":"Full 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 tr"},{"name":"move_layer","description":"Set 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/he"},{"name":"set_pivot","description":"Set 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 — "},{"name":"add_keyframe","description":"Add 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 t"},{"name":"set_keyframes_batch","description":"Add 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"},{"name":"add_keyframes","description":"Add 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"},{"name":"remove_keyframe","description":"Remove 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."},{"name":"shift_track","description":"Bulk-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 "},{"name":"set_track_loop","description":"Set the extrapolation mode for one property's animation track. Modes: \"hold\" (default — holds the boundary keyframe's value past the ends), \"loop\" (wraps frames"},{"name":"add_image_layer","description":"Add an image layer. The asset must already be uploaded (the editor's drag-drop, upload_image, create_upload_link, or POST /api/upload-asset/<projectId> with the"},{"name":"add_video_layer","description":"Add a video layer. The clip must already be uploaded (the morphareels-sdk's client.addVideo, or the editor). An upload returns { filename, name }: pass `filenam"},{"name":"add_shape","description":"Add a shape layer. `kind` selects the primitive: rect | ellipse | triangle | star | pentagon | hexagon | arrow | heart | rounded-rect | diamond | parallelogram "},{"name":"add_curve","description":"Draw 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 t"},{"name":"duplicate_layer","description":"Composition primitive: clone a leaf (image.<id> / video.<id> / shapes.<id> / text.<id>) `count` times, applying a cumulative per-step transform — copy i is offs"},{"name":"remove_layer","description":"Delete 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 back"},{"name":"reorder_layer","description":"Move 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"},{"name":"set_style","description":"Set 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 — inne"},{"name":"set_layer_fill","description":"Set 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 liter"},{"name":"set_text_background","description":"Add 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 optiona"},{"name":"set_group_box","description":"Set 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 ba"},{"name":"add_color_keyframe","description":"Add 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_backg"},{"name":"remove_color_keyframe","description":"Remove 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 ent"},{"name":"fade_layer","description":"Fade a layer's opacity between two frames in one call."},{"name":"set_layer_visible","description":"Show or hide a layer instantly by writing a single opacity keyframe (1 or 0) at frame 0."},{"name":"apply_preset","description":"Apply a canned animation preset to a layer."},{"name":"apply_preset_stagger","description":"Apply 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"},{"name":"group_layers","description":"Wrap sibling elements in a new group. USE THIS whenever several layers are one thing — a butterfly assembled from wings + body + antennae, an icon built from pr"},{"name":"ungroup_layers","description":"Dissolve 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"},{"name":"set_group_parent","description":"Move an element into a group (or out to root). Refuses to place a group inside its own descendants."},{"name":"rename_group","description":"Rename a group. Pure cosmetic — labels appear in the Inspector and describe_video output."},{"name":"add_to_collection","description":"Add 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 i"},{"name":"remove_from_collection","description":"Remove 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 "},{"name":"add_morpha_layer","description":"Embed 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"},{"name":"add_audio_overlay","description":"Add an audio overlay (mp3/m4a/wav/ogg) scheduled at a frame-aligned start. The asset must already be uploaded (upload_audio, create_upload_link, or the editor);"},{"name":"remove_audio_overlay","description":"Delete an audio overlay by id."},{"name":"update_audio_overlay","description":"Patch an existing audio overlay. Only the fields you pass are changed. Pass endFrame:null to clear it (revert to natural-length playback)."},{"name":"set_video_layer_trim","description":"Patch 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_sta"},{"name":"set_layer_block","description":"Set (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 keyf"},{"name":"set_layer_transition","description":"Set 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 l"},{"name":"move_band","description":"Place 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 "},{"name":"shift_group","description":"MOVE 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 "},{"name":"set_group_window","description":"TRIM 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: thi"},{"name":"set_duration","description":"Author an EXPLICIT composition length in seconds, pinning it (duration_authored=true) so the auto-fit no longer drives it. Morpha normally DERIVES the comp leng"},{"name":"fit_duration_to_content","description":"Clear 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-"},{"name":"cut_range","description":"Ripple-delete a time window [startFrame, endFrame) — remove that span and pull all later content earlier by delta = endFrame - startFrame (the NLE 'ripple delet"},{"name":"set_embed_origins","description":"Replace the project's embed allowlist — the hostnames permitted to load this project through the public <morpha-video> embed. Pass the full desired list; it ove"},{"name":"add_embed_origin","description":"Add 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"},{"name":"remove_embed_origin","description":"Remove 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 OF"},{"name":"set_custom_font","description":"Register 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 bui"},{"name":"list_fonts","description":"List available font families across every source the editor knows about (Google + Bunny + Fontshare + Fontsource + Velvetyne) PLUS the project's user-uploaded c"},{"name":"set_layer_text","description":"Edit 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"},{"name":"add_text_layer","description":"Create 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-lin"},{"name":"add_caption_track","description":"Build 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 "},{"name":"split_caption_line","description":"Split 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, "},{"name":"merge_caption_lines","description":"Merge 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. "},{"name":"rename_layer","description":"Set 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"},{"name":"set_loop","description":"Set 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,"},{"name":"set_canvas_size","description":"Resize 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"},{"name":"set_image_filename","description":"Repoint an existing image layer at a different uploaded asset — keeps the layer's id, position, size, animations, and styles; only the bitmap changes. The asset"},{"name":"set_video_clip","description":"Repoint 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 ch"},{"name":"set_matte_source","description":"Set (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 "},{"name":"freeze_frame","description":"Freeze 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"},{"name":"set_clip_speed","description":"Play 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 tri"},{"name":"add_speed_keyframe","description":"Add 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"},{"name":"remove_speed_keyframe","description":"Remove 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 th"},{"name":"set_video_layer_muted","description":"Mute 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 c"},{"name":"add_page","description":"Append 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 "},{"name":"delete_page","description":"Remove 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; "},{"name":"reorder_pages","description":"Move 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 b"},{"name":"select_page","description":"Switch which page is ACTIVE — the page the content tools target. Subsequent describe_video / inspect_layers / all content tools read and write this page until t"}],"toolCount":98,"toolsHash":"1d8058031c2b01dc98a73399106e95c50848d49eda2c44d35d2d4ab73e293a32","serverName":"morpha","capabilities":["tools"],"serverVersion":"1.0.375","protocolVersion":"2025-06-18"}},{"at":"2026-10-08T05:21:20.210Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":20,"error":null,"detail":{"tools":[{"name":"list_projects","description":"List 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 "},{"name":"list_workspaces","description":"List the workspaces (shared team spaces) the account belongs to, as { id, name, role, memberCount } entries. `role` is the caller's role in that workspace (owne"},{"name":"create_workspace","description":"Create 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 "},{"name":"rename_workspace","description":"Rename 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 w"},{"name":"open_project","description":"Get a tappable link that opens a project directly in the editor (the editor loads exactly this project from the URL). Returns { name, editorUrl }. Use this when"},{"name":"create_project","description":"Create a new project and return its server-minted id. The id is an opaque v4 UUID assigned automatically — you never choose or name it; refer to the project by "},{"name":"move_project_to_workspace","description":"Move 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 — ever"},{"name":"list_collection","description":"List the user's reusable Collection — layers they added with add_to_collection (lower-thirds, logo stings, brand intros, or any single layer). Returns { items: "},{"name":"add_from_collection","description":"Add 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"},{"name":"duplicate_project","description":"Duplicate 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 `proje"},{"name":"rename_project","description":"Update 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"},{"name":"reid_project","description":"Change 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), versio"},{"name":"delete_project","description":"Permanently delete a project: its JSON, shortlist, versions, uploaded assets, and uploaded clips. Requires edit access — the owner, a workspace editor/admin/own"},{"name":"save_version","description":"Save a version of the current project state. ALWAYS call this once after each meaningful set of changes you make on the user's behalf — the user flicks between "},{"name":"list_versions","description":"List every saved version of a project, newest-first. Returns summaries (id, name, timestamp, source, kind, version_number, cp) — not the inner project JSON. `ki"},{"name":"restore_version","description":"Load 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"},{"name":"rename_version","description":"Rename 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 cha"},{"name":"delete_version","description":"Permanently 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"},{"name":"upload_image","description":"Upload an image into a project by fetching a public http(s) URL server-side. The target is the URL you pass and nothing else: Morpha's Worker fetches it with a "},{"name":"upload_audio","description":"Upload an audio track into a project by fetching a public http(s) URL server-side — the ONLY way to get an audio file's bytes into a project over MCP/HTTP (add_"},{"name":"create_upload_link","description":"Put a file from the caller's own disk into a project: an image (.png/.jpg/.jpeg/.gif/.webp/.svg), an audio track (.mp3/.m4a/.wav/.ogg/.aac) or a font (.woff2/.w"},{"name":"detect_text_regions","description":"Return 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"},{"name":"safe_zones","description":"Return 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 'te"},{"name":"transcribe_clip","description":"Return 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 absen"},{"name":"clip_processing_status","description":"Report 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, aud"},{"name":"render_video","description":"Render 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', "},{"name":"render_status","description":"Check 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 a"},{"name":"describe_video","description":"Structural OVERVIEW of the composition (the table of contents) — canvas size, duration, platform_safe_area (the part of the canvas TikTok / Reels UI and Instagr"},{"name":"inspect_layers","description":"Full 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 tr"},{"name":"move_layer","description":"Set 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/he"},{"name":"set_pivot","description":"Set 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 — "},{"name":"add_keyframe","description":"Add 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 t"},{"name":"set_keyframes_batch","description":"Add 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"},{"name":"add_keyframes","description":"Add 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"},{"name":"remove_keyframe","description":"Remove 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."},{"name":"shift_track","description":"Bulk-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 "},{"name":"set_track_loop","description":"Set the extrapolation mode for one property's animation track. Modes: \"hold\" (default — holds the boundary keyframe's value past the ends), \"loop\" (wraps frames"},{"name":"add_image_layer","description":"Add an image layer. The asset must already be uploaded (the editor's drag-drop, upload_image, create_upload_link, or POST /api/upload-asset/<projectId> with the"},{"name":"add_video_layer","description":"Add a video layer. The clip must already be uploaded (the morphareels-sdk's client.addVideo, or the editor). An upload returns { filename, name }: pass `filenam"},{"name":"add_shape","description":"Add a shape layer. `kind` selects the primitive: rect | ellipse | triangle | star | pentagon | hexagon | arrow | heart | rounded-rect | diamond | parallelogram "},{"name":"add_curve","description":"Draw 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 t"},{"name":"duplicate_layer","description":"Composition primitive: clone a leaf (image.<id> / video.<id> / shapes.<id> / text.<id>) `count` times, applying a cumulative per-step transform — copy i is offs"},{"name":"remove_layer","description":"Delete 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 back"},{"name":"reorder_layer","description":"Move 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"},{"name":"set_style","description":"Set 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 — inne"},{"name":"set_layer_fill","description":"Set 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 liter"},{"name":"set_text_background","description":"Add 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 optiona"},{"name":"set_group_box","description":"Set 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 ba"},{"name":"add_color_keyframe","description":"Add 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_backg"},{"name":"remove_color_keyframe","description":"Remove 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 ent"},{"name":"fade_layer","description":"Fade a layer's opacity between two frames in one call."},{"name":"set_layer_visible","description":"Show or hide a layer instantly by writing a single opacity keyframe (1 or 0) at frame 0."},{"name":"apply_preset","description":"Apply a canned animation preset to a layer."},{"name":"apply_preset_stagger","description":"Apply 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"},{"name":"group_layers","description":"Wrap sibling elements in a new group. USE THIS whenever several layers are one thing — a butterfly assembled from wings + body + antennae, an icon built from pr"},{"name":"ungroup_layers","description":"Dissolve 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"},{"name":"set_group_parent","description":"Move an element into a group (or out to root). Refuses to place a group inside its own descendants."},{"name":"rename_group","description":"Rename a group. Pure cosmetic — labels appear in the Inspector and describe_video output."},{"name":"add_to_collection","description":"Add 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 i"},{"name":"remove_from_collection","description":"Remove 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 "},{"name":"add_morpha_layer","description":"Embed 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"},{"name":"add_audio_overlay","description":"Add an audio overlay (mp3/m4a/wav/ogg) scheduled at a frame-aligned start. The asset must already be uploaded (upload_audio, create_upload_link, or the editor);"},{"name":"remove_audio_overlay","description":"Delete an audio overlay by id."},{"name":"update_audio_overlay","description":"Patch an existing audio overlay. Only the fields you pass are changed. Pass endFrame:null to clear it (revert to natural-length playback)."},{"name":"set_video_layer_trim","description":"Patch 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_sta"},{"name":"set_layer_block","description":"Set (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 keyf"},{"name":"set_layer_transition","description":"Set 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 l"},{"name":"move_band","description":"Place 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 "},{"name":"shift_group","description":"MOVE 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 "},{"name":"set_group_window","description":"TRIM 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: thi"},{"name":"set_duration","description":"Author an EXPLICIT composition length in seconds, pinning it (duration_authored=true) so the auto-fit no longer drives it. Morpha normally DERIVES the comp leng"},{"name":"fit_duration_to_content","description":"Clear 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-"},{"name":"cut_range","description":"Ripple-delete a time window [startFrame, endFrame) — remove that span and pull all later content earlier by delta = endFrame - startFrame (the NLE 'ripple delet"},{"name":"set_embed_origins","description":"Replace the project's embed allowlist — the hostnames permitted to load this project through the public <morpha-video> embed. Pass the full desired list; it ove"},{"name":"add_embed_origin","description":"Add 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"},{"name":"remove_embed_origin","description":"Remove 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 OF"},{"name":"set_custom_font","description":"Register 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 bui"},{"name":"list_fonts","description":"List available font families across every source the editor knows about (Google + Bunny + Fontshare + Fontsource + Velvetyne) PLUS the project's user-uploaded c"},{"name":"set_layer_text","description":"Edit 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"},{"name":"add_text_layer","description":"Create 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-lin"},{"name":"add_caption_track","description":"Build 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 "},{"name":"split_caption_line","description":"Split 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, "},{"name":"merge_caption_lines","description":"Merge 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. "},{"name":"rename_layer","description":"Set 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"},{"name":"set_loop","description":"Set 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,"},{"name":"set_canvas_size","description":"Resize 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"},{"name":"set_image_filename","description":"Repoint an existing image layer at a different uploaded asset — keeps the layer's id, position, size, animations, and styles; only the bitmap changes. The asset"},{"name":"set_video_clip","description":"Repoint 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 ch"},{"name":"set_matte_source","description":"Set (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 "},{"name":"freeze_frame","description":"Freeze 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"},{"name":"set_clip_speed","description":"Play 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 tri"},{"name":"add_speed_keyframe","description":"Add 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"},{"name":"remove_speed_keyframe","description":"Remove 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 th"},{"name":"set_video_layer_muted","description":"Mute 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 c"},{"name":"add_page","description":"Append 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 "},{"name":"delete_page","description":"Remove 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; "},{"name":"reorder_pages","description":"Move 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 b"},{"name":"select_page","description":"Switch which page is ACTIVE — the page the content tools target. Subsequent describe_video / inspect_layers / all content tools read and write this page until t"}],"toolCount":98,"toolsHash":"1d8058031c2b01dc98a73399106e95c50848d49eda2c44d35d2d4ab73e293a32","serverName":"morpha","capabilities":["tools"],"serverVersion":"1.0.375","protocolVersion":"2025-06-18"}},{"at":"2026-10-07T22:24:17.864Z","kind":"mcp_initialize","ok":true,"httpStatus":200,"latencyMs":42,"error":null,"detail":{"tools":[{"name":"list_projects","description":"List 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 "},{"name":"list_workspaces","description":"List the workspaces (shared team spaces) the account belongs to, as { id, name, role, memberCount } entries. `role` is the caller's role in that workspace (owne"},{"name":"create_workspace","description":"Create 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 "},{"name":"rename_workspace","description":"Rename 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 w"},{"name":"open_project","description":"Get a tappable link that opens a project directly in the editor (the editor loads exactly this project from the URL). Returns { name, editorUrl }. Use this when"},{"name":"create_project","description":"Create a new project and return its server-minted id. The id is an opaque v4 UUID assigned automatically — you never choose or name it; refer to the project by "},{"name":"move_project_to_workspace","description":"Move 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 — ever"},{"name":"list_collection","description":"List the user's reusable Collection — layers they added with add_to_collection (lower-thirds, logo stings, brand intros, or any single layer). Returns { items: "},{"name":"add_from_collection","description":"Add 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"},{"name":"duplicate_project","description":"Duplicate 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 `proje"},{"name":"rename_project","description":"Update 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"},{"name":"reid_project","description":"Change 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), versio"},{"name":"delete_project","description":"Permanently delete a project: its JSON, shortlist, versions, uploaded assets, and uploaded clips. Requires edit access — the owner, a workspace editor/admin/own"},{"name":"save_version","description":"Save a version of the current project state. ALWAYS call this once after each meaningful set of changes you make on the user's behalf — the user flicks between "},{"name":"list_versions","description":"List every saved version of a project, newest-first. Returns summaries (id, name, timestamp, source, kind, version_number, cp) — not the inner project JSON. `ki"},{"name":"restore_version","description":"Load 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"},{"name":"rename_version","description":"Rename 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 cha"},{"name":"delete_version","description":"Permanently 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"},{"name":"upload_image","description":"Upload an image into a project by fetching a public http(s) URL server-side. The target is the URL you pass and nothing else: Morpha's Worker fetches it with a "},{"name":"upload_audio","description":"Upload an audio track into a project by fetching a public http(s) URL server-side — the ONLY way to get an audio file's bytes into a project over MCP/HTTP (add_"},{"name":"create_upload_link","description":"Put a file from the caller's own disk into a project: an image (.png/.jpg/.jpeg/.gif/.webp/.svg), an audio track (.mp3/.m4a/.wav/.ogg/.aac) or a font (.woff2/.w"},{"name":"detect_text_regions","description":"Return 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"},{"name":"safe_zones","description":"Return 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 'te"},{"name":"transcribe_clip","description":"Return 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 absen"},{"name":"clip_processing_status","description":"Report 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, aud"},{"name":"render_video","description":"Render 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', "},{"name":"render_status","description":"Check 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 a"},{"name":"describe_video","description":"Structural OVERVIEW of the composition (the table of contents) — canvas size, duration, platform_safe_area (the part of the canvas TikTok / Reels UI and Instagr"},{"name":"inspect_layers","description":"Full 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 tr"},{"name":"move_layer","description":"Set 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/he"},{"name":"set_pivot","description":"Set 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 — "},{"name":"add_keyframe","description":"Add 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 t"},{"name":"set_keyframes_batch","description":"Add 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"},{"name":"add_keyframes","description":"Add 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"},{"name":"remove_keyframe","description":"Remove 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."},{"name":"shift_track","description":"Bulk-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 "},{"name":"set_track_loop","description":"Set the extrapolation mode for one property's animation track. Modes: \"hold\" (default — holds the boundary keyframe's value past the ends), \"loop\" (wraps frames"},{"name":"add_image_layer","description":"Add an image layer. The asset must already be uploaded (the editor's drag-drop, upload_image, create_upload_link, or POST /api/upload-asset/<projectId> with the"},{"name":"add_video_layer","description":"Add a video layer. The clip must already be uploaded (the morphareels-sdk's client.addVideo, or the editor). An upload returns { filename, name }: pass `filenam"},{"name":"add_shape","description":"Add a shape layer. `kind` selects the primitive: rect | ellipse | triangle | star | pentagon | hexagon | arrow | heart | rounded-rect | diamond | parallelogram "},{"name":"add_curve","description":"Draw 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 t"},{"name":"duplicate_layer","description":"Composition primitive: clone a leaf (image.<id> / video.<id> / shapes.<id> / text.<id>) `count` times, applying a cumulative per-step transform — copy i is offs"},{"name":"remove_layer","description":"Delete 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 back"},{"name":"reorder_layer","description":"Move 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"},{"name":"set_style","description":"Set 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 — inne"},{"name":"set_layer_fill","description":"Set 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 liter"},{"name":"set_text_background","description":"Add 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 optiona"},{"name":"set_group_box","description":"Set 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 ba"},{"name":"add_color_keyframe","description":"Add 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_backg"},{"name":"remove_color_keyframe","description":"Remove 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 ent"},{"name":"fade_layer","description":"Fade a layer's opacity between two frames in one call."},{"name":"set_layer_visible","description":"Show or hide a layer instantly by writing a single opacity keyframe (1 or 0) at frame 0."},{"name":"apply_preset","description":"Apply a canned animation preset to a layer."},{"name":"apply_preset_stagger","description":"Apply 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"},{"name":"group_layers","description":"Wrap sibling elements in a new group. USE THIS whenever several layers are one thing — a butterfly assembled from wings + body + antennae, an icon built from pr"},{"name":"ungroup_layers","description":"Dissolve 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"},{"name":"set_group_parent","description":"Move an element into a group (or out to root). Refuses to place a group inside its own descendants."},{"name":"rename_group","description":"Rename a group. Pure cosmetic — labels appear in the Inspector and describe_video output."},{"name":"add_to_collection","description":"Add 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 i"},{"name":"remove_from_collection","description":"Remove 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 "},{"name":"add_morpha_layer","description":"Embed 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"},{"name":"add_audio_overlay","description":"Add an audio overlay (mp3/m4a/wav/ogg) scheduled at a frame-aligned start. The asset must already be uploaded (upload_audio, create_upload_link, or the editor);"},{"name":"remove_audio_overlay","description":"Delete an audio overlay by id."},{"name":"update_audio_overlay","description":"Patch an existing audio overlay. Only the fields you pass are changed. Pass endFrame:null to clear it (revert to natural-length playback)."},{"name":"set_video_layer_trim","description":"Patch 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_sta"},{"name":"set_layer_block","description":"Set (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 keyf"},{"name":"set_layer_transition","description":"Set 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 l"},{"name":"move_band","description":"Place 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 "},{"name":"shift_group","description":"MOVE 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 "},{"name":"set_group_window","description":"TRIM 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: thi"},{"name":"set_duration","description":"Author an EXPLICIT composition length in seconds, pinning it (duration_authored=true) so the auto-fit no longer drives it. Morpha normally DERIVES the comp leng"},{"name":"fit_duration_to_content","description":"Clear 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-"},{"name":"cut_range","description":"Ripple-delete a time window [startFrame, endFrame) — remove that span and pull all later content earlier by delta = endFrame - startFrame (the NLE 'ripple delet"},{"name":"set_embed_origins","description":"Replace the project's embed allowlist — the hostnames permitted to load this project through the public <morpha-video> embed. Pass the full desired list; it ove"},{"name":"add_embed_origin","description":"Add 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"},{"name":"remove_embed_origin","description":"Remove 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 OF"},{"name":"set_custom_font","description":"Register 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 bui"},{"name":"list_fonts","description":"List available font families across every source the editor knows about (Google + Bunny + Fontshare + Fontsource + Velvetyne) PLUS the project's user-uploaded c"},{"name":"set_layer_text","description":"Edit 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"},{"name":"add_text_layer","description":"Create 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-lin"},{"name":"add_caption_track","description":"Build 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 "},{"name":"split_caption_line","description":"Split 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, "},{"name":"merge_caption_lines","description":"Merge 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. "},{"name":"rename_layer","description":"Set 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"},{"name":"set_loop","description":"Set 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,"},{"name":"set_canvas_size","description":"Resize 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"},{"name":"set_image_filename","description":"Repoint an existing image layer at a different uploaded asset — keeps the layer's id, position, size, animations, and styles; only the bitmap changes. The asset"},{"name":"set_video_clip","description":"Repoint 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 ch"},{"name":"set_matte_source","description":"Set (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 "},{"name":"freeze_frame","description":"Freeze 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"},{"name":"set_clip_speed","description":"Play 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 tri"},{"name":"add_speed_keyframe","description":"Add 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"},{"name":"remove_speed_keyframe","description":"Remove 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 th"},{"name":"set_video_layer_muted","description":"Mute 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 c"},{"name":"add_page","description":"Append 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 "},{"name":"delete_page","description":"Remove 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; "},{"name":"reorder_pages","description":"Move 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 b"},{"name":"select_page","description":"Switch which page is ACTIVE — the page the content tools target. Subsequent describe_video / inspect_layers / all content tools read and write this page until t"}],"toolCount":98,"toolsHash":"1d8058031c2b01dc98a73399106e95c50848d49eda2c44d35d2d4ab73e293a32","serverName":"morpha","capabilities":["tools"],"serverVersion":"1.0.375","protocolVersion":"2025-06-18"}}],"tools":[{"name":"list_projects","description":"List 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 "},{"name":"list_workspaces","description":"List the workspaces (shared team spaces) the account belongs to, as { id, name, role, memberCount } entries. `role` is the caller's role in that workspace (owne"},{"name":"create_workspace","description":"Create 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 "},{"name":"rename_workspace","description":"Rename 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 w"},{"name":"open_project","description":"Get a tappable link that opens a project directly in the editor (the editor loads exactly this project from the URL). Returns { name, editorUrl }. Use this when"},{"name":"create_project","description":"Create a new project and return its server-minted id. The id is an opaque v4 UUID assigned automatically — you never choose or name it; refer to the project by "},{"name":"move_project_to_workspace","description":"Move 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 — ever"},{"name":"list_collection","description":"List the user's reusable Collection — layers they added with add_to_collection (lower-thirds, logo stings, brand intros, or any single layer). Returns { items: "},{"name":"add_from_collection","description":"Add 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"},{"name":"duplicate_project","description":"Duplicate 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 `proje"},{"name":"rename_project","description":"Update 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"},{"name":"reid_project","description":"Change 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), versio"},{"name":"delete_project","description":"Permanently delete a project: its JSON, shortlist, versions, uploaded assets, and uploaded clips. Requires edit access — the owner, a workspace editor/admin/own"},{"name":"save_version","description":"Save a version of the current project state. ALWAYS call this once after each meaningful set of changes you make on the user's behalf — the user flicks between "},{"name":"list_versions","description":"List every saved version of a project, newest-first. Returns summaries (id, name, timestamp, source, kind, version_number, cp) — not the inner project JSON. `ki"},{"name":"restore_version","description":"Load 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"},{"name":"rename_version","description":"Rename 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 cha"},{"name":"delete_version","description":"Permanently 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"},{"name":"upload_image","description":"Upload an image into a project by fetching a public http(s) URL server-side. The target is the URL you pass and nothing else: Morpha's Worker fetches it with a "},{"name":"upload_audio","description":"Upload an audio track into a project by fetching a public http(s) URL server-side — the ONLY way to get an audio file's bytes into a project over MCP/HTTP (add_"},{"name":"create_upload_link","description":"Put a file from the caller's own disk into a project: an image (.png/.jpg/.jpeg/.gif/.webp/.svg), an audio track (.mp3/.m4a/.wav/.ogg/.aac) or a font (.woff2/.w"},{"name":"detect_text_regions","description":"Return 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"},{"name":"safe_zones","description":"Return 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 'te"},{"name":"transcribe_clip","description":"Return 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 absen"},{"name":"clip_processing_status","description":"Report 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, aud"},{"name":"render_video","description":"Render 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', "},{"name":"render_status","description":"Check 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 a"},{"name":"describe_video","description":"Structural OVERVIEW of the composition (the table of contents) — canvas size, duration, platform_safe_area (the part of the canvas TikTok / Reels UI and Instagr"},{"name":"inspect_layers","description":"Full 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 tr"},{"name":"move_layer","description":"Set 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/he"},{"name":"set_pivot","description":"Set 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 — "},{"name":"add_keyframe","description":"Add 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 t"},{"name":"set_keyframes_batch","description":"Add 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"},{"name":"add_keyframes","description":"Add 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"},{"name":"remove_keyframe","description":"Remove 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."},{"name":"shift_track","description":"Bulk-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 "},{"name":"set_track_loop","description":"Set the extrapolation mode for one property's animation track. Modes: \"hold\" (default — holds the boundary keyframe's value past the ends), \"loop\" (wraps frames"},{"name":"add_image_layer","description":"Add an image layer. The asset must already be uploaded (the editor's drag-drop, upload_image, create_upload_link, or POST /api/upload-asset/<projectId> with the"},{"name":"add_video_layer","description":"Add a video layer. The clip must already be uploaded (the morphareels-sdk's client.addVideo, or the editor). An upload returns { filename, name }: pass `filenam"},{"name":"add_shape","description":"Add a shape layer. `kind` selects the primitive: rect | ellipse | triangle | star | pentagon | hexagon | arrow | heart | rounded-rect | diamond | parallelogram "},{"name":"add_curve","description":"Draw 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 t"},{"name":"duplicate_layer","description":"Composition primitive: clone a leaf (image.<id> / video.<id> / shapes.<id> / text.<id>) `count` times, applying a cumulative per-step transform — copy i is offs"},{"name":"remove_layer","description":"Delete 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 back"},{"name":"reorder_layer","description":"Move 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"},{"name":"set_style","description":"Set 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 — inne"},{"name":"set_layer_fill","description":"Set 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 liter"},{"name":"set_text_background","description":"Add 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 optiona"},{"name":"set_group_box","description":"Set 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 ba"},{"name":"add_color_keyframe","description":"Add 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_backg"},{"name":"remove_color_keyframe","description":"Remove 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 ent"},{"name":"fade_layer","description":"Fade a layer's opacity between two frames in one call."},{"name":"set_layer_visible","description":"Show or hide a layer instantly by writing a single opacity keyframe (1 or 0) at frame 0."},{"name":"apply_preset","description":"Apply a canned animation preset to a layer."},{"name":"apply_preset_stagger","description":"Apply 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"},{"name":"group_layers","description":"Wrap sibling elements in a new group. USE THIS whenever several layers are one thing — a butterfly assembled from wings + body + antennae, an icon built from pr"},{"name":"ungroup_layers","description":"Dissolve 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"},{"name":"set_group_parent","description":"Move an element into a group (or out to root). Refuses to place a group inside its own descendants."},{"name":"rename_group","description":"Rename a group. Pure cosmetic — labels appear in the Inspector and describe_video output."},{"name":"add_to_collection","description":"Add 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 i"},{"name":"remove_from_collection","description":"Remove 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 "},{"name":"add_morpha_layer","description":"Embed 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"},{"name":"add_audio_overlay","description":"Add an audio overlay (mp3/m4a/wav/ogg) scheduled at a frame-aligned start. The asset must already be uploaded (upload_audio, create_upload_link, or the editor);"},{"name":"remove_audio_overlay","description":"Delete an audio overlay by id."},{"name":"update_audio_overlay","description":"Patch an existing audio overlay. Only the fields you pass are changed. Pass endFrame:null to clear it (revert to natural-length playback)."},{"name":"set_video_layer_trim","description":"Patch 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_sta"},{"name":"set_layer_block","description":"Set (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 keyf"},{"name":"set_layer_transition","description":"Set 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 l"},{"name":"move_band","description":"Place 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 "},{"name":"shift_group","description":"MOVE 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 "},{"name":"set_group_window","description":"TRIM 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: thi"},{"name":"set_duration","description":"Author an EXPLICIT composition length in seconds, pinning it (duration_authored=true) so the auto-fit no longer drives it. Morpha normally DERIVES the comp leng"},{"name":"fit_duration_to_content","description":"Clear 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-"},{"name":"cut_range","description":"Ripple-delete a time window [startFrame, endFrame) — remove that span and pull all later content earlier by delta = endFrame - startFrame (the NLE 'ripple delet"},{"name":"set_embed_origins","description":"Replace the project's embed allowlist — the hostnames permitted to load this project through the public <morpha-video> embed. Pass the full desired list; it ove"},{"name":"add_embed_origin","description":"Add 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"},{"name":"remove_embed_origin","description":"Remove 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 OF"},{"name":"set_custom_font","description":"Register 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 bui"},{"name":"list_fonts","description":"List available font families across every source the editor knows about (Google + Bunny + Fontshare + Fontsource + Velvetyne) PLUS the project's user-uploaded c"},{"name":"set_layer_text","description":"Edit 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"},{"name":"add_text_layer","description":"Create 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-lin"},{"name":"add_caption_track","description":"Build 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 "},{"name":"split_caption_line","description":"Split 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, "},{"name":"merge_caption_lines","description":"Merge 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. "},{"name":"rename_layer","description":"Set 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"},{"name":"set_loop","description":"Set 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,"},{"name":"set_canvas_size","description":"Resize 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"},{"name":"set_image_filename","description":"Repoint an existing image layer at a different uploaded asset — keeps the layer's id, position, size, animations, and styles; only the bitmap changes. The asset"},{"name":"set_video_clip","description":"Repoint 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 ch"},{"name":"set_matte_source","description":"Set (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 "},{"name":"freeze_frame","description":"Freeze 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"},{"name":"set_clip_speed","description":"Play 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 tri"},{"name":"add_speed_keyframe","description":"Add 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"},{"name":"remove_speed_keyframe","description":"Remove 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 th"},{"name":"set_video_layer_muted","description":"Mute 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 c"},{"name":"add_page","description":"Append 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 "},{"name":"delete_page","description":"Remove 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; "},{"name":"reorder_pages","description":"Move 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 b"},{"name":"select_page","description":"Switch which page is ACTIVE — the page the content tools target. Subsequent describe_video / inspect_layers / all content tools read and write this page until t"}],"package":null,"toolSurface":{"id":"ts_4wa5bdsvdxku","endpointId":"ep_rrjf8bsvcnvs","hash":"1d8058031c2b01dc98a73399106e95c50848d49eda2c44d35d2d4ab73e293a32","toolCount":98,"serverName":"morpha","serverVersion":"1.0.375","protocolVersion":"2025-06-18","firstSeenAt":"2026-09-30T20:28:13.800Z","lastSeenAt":"2026-10-10T10:27:28.261Z","observations":36,"toolNames":["list_projects","list_workspaces","create_workspace","rename_workspace","open_project","create_project","move_project_to_workspace","list_collection","add_from_collection","duplicate_project","rename_project","reid_project","delete_project","save_version","list_versions","restore_version","rename_version","delete_version","upload_image","upload_audio","create_upload_link","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","add_morpha_layer","add_audio_overlay","remove_audio_overlay","update_audio_overlay","set_video_layer_trim","set_layer_block","set_layer_transition","move_band","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"],"distinctSurfaces":8},"endpointFacts":[{"id":"ep_rrjf8bsvcnvs","url":"https://morphareels.ai/mcp","type":"mcp_streamable_http","factsCheckedAt":"2026-10-10T10:27:28.270Z","auth":{"observedAt":"2026-10-10T10:27:28.303Z","authRequired":false,"scheme":null,"resourceMetadata":{"url":"https://morphareels.ai/.well-known/oauth-protected-resource/mcp","resource":"https://morphareels.ai/mcp","authorizationServers":["https://morphareels.ai"],"scopesSupported":[]},"authorizationServer":{"url":"https://morphareels.ai/.well-known/oauth-authorization-server","issuer":"https://morphareels.ai","grantTypesSupported":["authorization_code"],"codeChallengeMethodsSupported":["S256"],"tokenEndpointAuthMethodsSupported":["none"],"dynamicClientRegistration":true,"dpopSigningAlgValuesSupported":[],"clientIdMetadataDocumentSupported":true},"conformance":{"dpop":false,"rfc8414":true,"rfc9728":true,"pkceS256":true,"clientIdMetadataDocument":true,"dynamicClientRegistration":true}},"tls":{"observedAt":"2026-10-10T10:27:28.318Z","protocol":"TLSv1.3","chainValid":true,"chainError":null,"hostMatches":true,"subject":"morphareels.ai","issuer":{"commonName":"WE1","organization":"Google Trust Services"},"validFrom":"2026-08-17T11:57:17.000Z","validTo":"2026-11-15T12:57:14.000Z","daysToExpiry":35,"sanCount":2,"fingerprint256":"A6:5B:08:27:AE:66:7C:28:58:A9:5B:4A:DD:D2:9D:C1:D2:E6:A0:64:A3:A8:EB:22:DA:E0:7A:08:28:A9:6D:3F"}}]},"verification":{"claimed":false,"claimedAt":null,"proofs":[]},"provenance":{"sources":[{"source":"mcp_registry","key":"ai.morphareels/morpha","url":"https://registry.modelcontextprotocol.io/v0/servers/ai.morphareels%2Fmorpha","firstSeenAt":"2026-09-09T11:26:40.740Z","fetchedAt":"2026-10-08T23:18:54.503Z","normalizedAt":"2026-10-08T23:18:54.503Z"}]},"firstSeenAt":"2026-09-09T11:26:40.740Z","updatedAt":"2026-10-10T10:30:29.720Z"}