Skip to main content
Glama

Get clip details

get_clip
Read-only

Read one clip: its elements (positions/sizes in canvas pixels), voiceover (text, voice, duration, voiceover_volume), background and transition. Pass render to also get a PNG of the frame.

ASK FOR WHAT YOU NEED. A full read is large — on a dense clip the per-word voiceover array and the element type_data blobs dominate it, and repeated full reads are the main way a long session runs out of context. select returns exactly the parts you name:

select: ['elements.x','elements.y','elements.width','elements.height'] → geometry only, to fix a layout select: ['elements.name','elements.start_time','elements.end_time'] → a timing pass select: ['voiceover_words'] → word timings only, to sync visuals to narration select: ['elements.textdata','voiceover_words'] → rewrite copy against the VO select: ['elements'] → whole element rows, no words select: ['groups'] → group rows only, to get a group_id for update_groups select: [] → no JSON at all (pair with render for the PNG alone — smallest read) (omit select) → everything; fine for a first look, expensive to repeat

render is the other output, and it is separate from select: select shapes the JSON, render produces a PNG.

render: {} → the frame at t=0 render: { timestamps: 2.5 } → the frame 2.5s into the clip render: { timestamps: [0.5, 2, 4] } → those three moments as ONE labelled grid render: { timestamps: [...], layout:'separate'} → the same moments as full-size images (~4x the tokens) render: { save: true } → also uploads the frame and returns presigned_url render: { element_id: 'e1' } → just that animation, cropped, at four moments (a fast check) render: { filmstrip: true } → the whole clip, every element, as 12 frames in one image with a timeline of cues, actions and element starts/ends render: { animation: '@org/cast', entry: 'maya' } → that entry alone on this clip, nothing placed or saved render: { preview: '' } → an edit_animation dry run, cropped, at four moments (a render of one animation also returns { warnings }: a glow cut at its box, text off its frame, frames near the limits) render: { element_id: 'e1', actions: [{name:'wave', start: 1}], cues: {reveal: 2} } → try settings, tracks, actions or cues without saving render: { max_width: 1280 } → a sharper frame when you must read small print select: [], render: {} → the PNG alone, no JSON (the default for a render of one animation) select: ['elements'], render: {} → element rows AND the frame

A single frame renders 960px wide by default — legible for this design system and about half the tokens of a 1280 frame. A GRID defaults to 1280, because that is the width of the whole grid and a third of 960 would leave each cell unreadable. Either way max_width caps the image you get back; raise it only to read genuinely small print. For a render of one animation (element_id, animation + entry, preview) it sets the resolution the canvas is drawn at before the crop: each cropped cell of the sheet is at most 360px wide whatever you pass, so a higher max_width sharpens a small element but does not enlarge the sheet. To look closer, use layout:'separate' (full-size crops) or render the clip frame without element_id.

Omitting render renders nothing. timestamps, layout and save live inside it because they only mean anything for a render — there is no way to ask for them without asking for the image.

element_ids is the other axis: it picks WHICH element rows come back, independently of select. Combine them for the leanest read — e.g. element_ids: ['el_9'], select: ['elements.x','elements.y'].

Element shape: universal wrapper fields (id, element_type, name, x, y, width, height, start_time, end_time, rotation) plus type-specific data (textdata/shapedata/imagedata/videodata/zoomdata) plus an optional keyframes array when animated. Keyframes come back in the same flat wire shape add_elements takes — { timestamp, positionX?, positionY?, width?, height?, interpolation? } in canvas pixels — so you can round-trip read → edit → update_elements without reshaping.

An animation element that comes from an animation carries an animation block: { ref, entry, rev, live, updateAvailable, behind?, dependencyUpdates?, manifest: { params, actions: [{name, args, default_duration}], cues, presets, parts } }. That manifest is the vocabulary for update_elements' param_tracks / actions / cues, e.g. update_elements({updates:[{element_id, type_data:{cues:[{op:'set', key, t}]}}]}) — plan against it, not against the code. behind: {rev, latest} means the element still shows an older revision than the animation's latest passing one (placed elements stay on their revision until moved, unless they follow Live); dependencyUpdates: [{animation, pinnedRev, latestRev, through?}] means its animation imports, directly or through others, an older revision of another animation (through names the older one whose chain also re-pins it). The top-level warnings then says how many elements are affected and names the call that moves them: animation.update_uses('', {project}) or animation.update_dependents('', {project}) in run_script.

Clip-level fields include transition (the current transition object — sibling of the update_clips transition arg; null if none) and voiceover_words (per-word timestamps, {word, start, end, punctuated_word} with start/end in SECONDS; null on clips with no transcription).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
renderNoRender the clip as an image. Presence of this object IS the request to render — omit it and nothing is rendered. `{}` renders at t=0. Independent of `select`, which only shapes the JSON: pair `select: []` with `render: {}` for the PNG alone (smallest read). CHECKING YOUR WORK: pass `timestamps` with SEVERAL mid-clip moments, not the t=0 default — text and image elements have entry animations (a ~0.4s slide/fade by default), so at t=0 they have not arrived yet and a correct edit renders as an empty frame, while shapes have no entry animation and do show at t=0. That mix is what makes a single t=0 render actively misleading: some elements appear and others don't. A list comes back as one grid for about a quarter of the tokens of the same frames separately, so checking several moments is the cheap option, not the expensive one. `animations: false` draws everything settled if you would rather not pick moments at all.
selectNoAsk for exactly the JSON you want, GraphQL-style. Omit for everything; pass [] for none. Sections: 'elements' (whole element rows), 'voiceover_words' (per-word VO timings; returns the key of the same name, holding `{word, start, end, punctuated_word}` with start/end in SECONDS — null on a clip with no transcription), 'groups' (group rows: id, name, parent_id, bounds_px, anchor_px, keyframes), 'busy' (generations still writing to this clip, as `[{entity_path, job_type}]` — EMPTY means nothing is pending, which is how you know a voiceover or AI image has landed; it is the same lock that would refuse your write, so a non-empty list also tells you what not to touch yet). Each section returns the key it is named after. Rendering is `render`, not a value here. Per-key: 'elements.<key>' projects element rows to just those keys (id is always kept). Keys: name, element_type, x, y, width, height, start_time, end_time, rotation, keyframes, textdata, shapedata, imagedata, videodata, zoomdata, codedata, avatardata, parent_id, dropShadows, animation. y_top is text-only and present ONLY when alignment is 'center' — the unambiguous TOP edge, which is exactly the case where `y` is NOT the top but the vertical CENTRE. To put that position back, send it as `y` with y_anchor:'top'. Examples: ['elements.x','elements.y','elements.width','elements.height'] to read geometry; ['elements.name','elements.start_time','elements.end_time'] for a timing pass; ['voiceover_words'] to sync visuals to narration; ['groups'] to resolve a group id for update_groups; [] with render:{} returns the PNG with no JSON (smallest read); ['elements.textdata','voiceover_words'] to rewrite copy against the VO. Mixing 'elements' with 'elements.<key>' returns whole rows. Use element_ids to choose WHICH rows — that is independent of this.
clip_indexYesZero-based clip index
project_idYesThe project ID
element_idsNoWHICH element rows to return — all others are dropped, and [] returns none. Independent of `select`, which chooses the sections/keys. Use it to re-inspect just what you added or updated; most add_elements/update_elements already echo the element's resolved layout, so often you don't need this at all.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed16 schema fields changed
    • changedInput schema / properties / element_ids / description
      Previous value: -"WHICH element rows to return — all others are dropped. Independent of `select`, which chooses the sections/keys. Use it to re-inspect just what you added or updated; most add_elements/update_elements already echo the element's resolved layout, so often you don't need this at all."New value: +"WHICH element rows to return — all others are dropped, and [] returns none. Independent of `select`, which chooses the sections/keys. Use it to re-inspect just what you added or updated; most add_elements/update_elements already echo the element's resolved layout, so often you don't need this at all."
    • addedInput schema / properties / render / properties / actions
      Added value: +{
      +  "description": "Action clips to try, [{name, start, duration?, args?}], element-local seconds; duration defaults to the action's default_duration. Replaces the element's clips for this render.",
      +  "items": {
      +    "properties": {
      +      "args": {
      +        "additionalProperties": {},
      +        "propertyNames": {
      +          "type": "string"
      +        },
      +        "type": "object"
      +      },
      +      "duration": {
      +        "type": "number"
      +      },
      +      "name": {
      +        "minLength": 1,
      +        "type": "string"
      +      },
      +      "start": {
      +        "type": "number"
      +      }
      +    },
      +    "required": [
      +      "name",
      +      "start"
      +    ],
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • addedInput schema / properties / render / properties / animation
      Added value: +{
      +  "description": "With entry: render that animation entry on its own on this clip at its latest revision (or rev), without placing or saving anything. Pair with parameter_values, duration and timestamps as needed.",
      +  "minLength": 1,
      +  "type": "string"
      +}
    • addedInput schema / properties / render / properties / cues
      Added value: +{
      +  "additionalProperties": {
      +    "type": "number"
      +  },
      +  "description": "Cue times to try, key: seconds (element-local).",
      +  "propertyNames": {
      +    "type": "string"
      +  },
      +  "type": "object"
      +}
    • addedInput schema / properties / render / properties / duration
      Added value: +{
      +  "description": "With animation, or a preview without an element: how long it plays, in seconds (default its default_duration).",
      +  "exclusiveMinimum": 0,
      +  "type": "number"
      +}
    • addedInput schema / properties / render / properties / element_id
      Added value: +{
      +  "description": "Render only this element over the clip's background, each moment cropped to its box, as one small sheet: the fast way to check one animation. Without timestamps it shows four moments across the element.",
      +  "minLength": 1,
      +  "type": "string"
      +}
    • addedInput schema / properties / render / properties / entry
      Added value: +{
      +  "description": "With animation: the entry to render.",
      +  "minLength": 1,
      +  "type": "string"
      +}
    • addedInput schema / properties / render / properties / filmstrip
      Added value: +{
      +  "anyOf": [
      +    {
      +      "const": true,
      +      "type": "boolean"
      +    },
      +    {
      +      "additionalProperties": false,
      +      "properties": {
      +        "around": {
      +          "description": "Center the window on a cue ('cue:land') or an action clip ('action:hop', by name or id) instead of from/to. On a whole clip, a cue several animations share is 'cue:land@<element name>'.",
      +          "pattern": "^(cue|action):.+",
      +          "type": "string"
      +        },
      +        "fps": {
      +          "description": "Frames per second instead of a count, e.g. 8 over 2 s is 17, capped at 16.",
      +          "exclusiveMinimum": 0,
      +          "maximum": 60,
      +          "type": "number"
      +        },
      +        "frames": {
      +          "description": "How many frames across the window, first and last included (default 12, max 16).",
      +          "maximum": 16,
      +          "minimum": 2,
      +          "type": "integer"
      +        },
      +        "from": {
      +          "description": "Window start, element-local seconds, or clip seconds for a whole clip (default 0).",
      +          "minimum": 0,
      +          "type": "number"
      +        },
      +        "seam": {
      +          "const": true,
      +          "description": "get_clip of a whole clip only: the cut from this clip into the next, as the player shows it: this clip's last second, its transition, and the next clip's first second, in this clip's seconds; takes frames, no window. Every cut of the film at once: get_project({render: {seams: true}}).",
      +          "type": "boolean"
      +        },
      +        "span": {
      +          "description": "With around: the window's length in seconds (default 2 around a cue; the clip plus 0.5 around an action).",
      +          "exclusiveMinimum": 0,
      +          "type": "number"
      +        },
      +        "to": {
      +          "description": "Window end, element-local seconds, or clip seconds for a whole clip (default the end).",
      +          "exclusiveMinimum": 0,
      +          "type": "number"
      +        }
      +      },
      +      "type": "object"
      +    }
      +  ],
      +  "description": "See motion in ONE image instead of four moments: frames spaced evenly across a window (default 12 over the whole animation), in reading order, each labelled with its element-local time, with a thin timeline under each row marking the frames, cues (lines) and action clips (bars). Read pacing, easing and whether a beat lands on its cue from it. true for the defaults, or {from, to} / {around: 'cue:<key>' | 'action:<name>', span}, with frames or fps. Replaces timestamps. On get_clip without element_id, animation or preview it shows the whole clip, every element drawn, in clip seconds, and the timeline also ticks each element's start and end."
      +}
    • changedInput schema / properties / render / properties / max_width / description
      Previous value: -"Width in px of the IMAGE you get back — for a grid that is the whole grid, not each cell, so a cell is roughly a third of it. Default 960 for a single frame, 1280 for a grid. An image costs roughly (w×h)/750 tokens, so 1280 is ~1229 and 960 ~691; above 1568 nothing is gained because the model downscales to that anyway. The default is legible for this design system's type scale — a 40px canvas caption lands at ~20px. Raise it only to read genuinely small print, such as UI text inside a pasted screenshot."New value: +"Width in px of the image you get back: default 960 for a single frame, 1280 for a grid (the whole grid; a cell is about a third). An image costs about (w×h)/750 tokens and nothing is gained above 1568. On a render of one animation it is the resolution drawn before the crop; sheet cells stay at most 360px wide."
    • addedInput schema / properties / render / properties / param_tracks
      Added value: +{
      +  "additionalProperties": {
      +    "items": {
      +      "properties": {
      +        "interpolation": {
      +          "type": "string"
      +        },
      +        "t": {
      +          "type": "number"
      +        },
      +        "value": {}
      +      },
      +      "required": [
      +        "t",
      +        "value"
      +      ],
      +      "type": "object"
      +    },
      +    "minItems": 1,
      +    "type": "array"
      +  },
      +  "description": "Keyframed settings to try, key: [{t, value, interpolation?}], t in element-local seconds. Replaces that key's track for this render.",
      +  "propertyNames": {
      +    "type": "string"
      +  },
      +  "type": "object"
      +}
    • addedInput schema / properties / render / properties / parameter_values
      Added value: +{
      +  "additionalProperties": {},
      +  "description": "Settings to render with, merged over the element's own (key: value).",
      +  "propertyNames": {
      +    "type": "string"
      +  },
      +  "type": "object"
      +}
    • addedInput schema / properties / render / properties / preview
      Added value: +{
      +  "description": "A preview token from edit_animation(dry_run: true): renders the unsaved code on its own as one sheet, cropped like element_id — the element the edit was about, or, for a dry run without an element, the entry alone on this clip. Nothing is saved.",
      +  "minLength": 1,
      +  "type": "string"
      +}
    • addedInput schema / properties / render / properties / rev
      Added value: +{
      +  "description": "With animation: the revision to render.",
      +  "maximum": 9007199254740991,
      +  "minimum": 1,
      +  "type": "integer"
      +}
    • addedInput schema / properties / render / properties / video
      Added value: +{
      +  "description": "For people: also a short low-resolution MP4 of the same window (the filmstrip's, or the animation's first 10 s), returned as video_url, a link valid for an hour. You can't watch it: judge motion from the filmstrip.",
      +  "type": "boolean"
      +}
    • changedInput schema / properties / select / description
      Previous value: -"Ask for exactly the JSON you want, GraphQL-style. Omit for everything; pass [] for none.\n  Sections: 'elements' (whole element rows), 'voiceover_words' (per-word VO timings; returns the key of the same name, holding `{word, start, end, punctuated_word}` with start/end in SECONDS — null on a clip with no transcription), 'groups' (group rows: id, name, parent_id, bounds_px, anchor_px, keyframes), 'busy' (generations still writing to this clip, as `[{entity_path, job_type}]` — EMPTY means nothing is pending, which is how you know a voiceover or AI image has landed; it is the same lock that would refuse your write, so a non-empty list also tells you what not to touch yet). Each section returns the key it is named after. Rendering is `render`, not a value here.\n  Per-key: 'elements.<key>' projects element rows to just those keys (id is always kept). Keys: name, element_type, x, y, width, height, start_time, end_time, rotation, keyframes, textdata, shapedata, imagedata, videodata, zoomdata, codedata, avatardata, parent_id, dropShadows. y_top is text-only and present ONLY when alignment is 'center' — the unambiguous TOP edge, which is exactly the case where `y` is NOT the top but the vertical CENTRE. To put that position back, send it as `y` with y_anchor:'top'.\n  Examples: ['elements.x','elements.y','elements.width','elements.height'] to read geometry; ['elements.name','elements.start_time','elements.end_time'] for a timing pass; ['voiceover_words'] to sync visuals to narration; ['groups'] to resolve a group id for update_groups; [] with render:{} returns the PNG with no JSON (smallest read); ['elements.textdata','voiceover_words'] to rewrite copy against the VO.\n  Mixing 'elements' with 'elements.<key>' returns whole rows. Use element_ids to choose WHICH rows — that is independent of this."New value: +"Ask for exactly the JSON you want, GraphQL-style. Omit for everything; pass [] for none.\n  Sections: 'elements' (whole element rows), 'voiceover_words' (per-word VO timings; returns the key of the same name, holding `{word, start, end, punctuated_word}` with start/end in SECONDS — null on a clip with no transcription), 'groups' (group rows: id, name, parent_id, bounds_px, anchor_px, keyframes), 'busy' (generations still writing to this clip, as `[{entity_path, job_type}]` — EMPTY means nothing is pending, which is how you know a voiceover or AI image has landed; it is the same lock that would refuse your write, so a non-empty list also tells you what not to touch yet). Each section returns the key it is named after. Rendering is `render`, not a value here.\n  Per-key: 'elements.<key>' projects element rows to just those keys (id is always kept). Keys: name, element_type, x, y, width, height, start_time, end_time, rotation, keyframes, textdata, shapedata, imagedata, videodata, zoomdata, codedata, avatardata, parent_id, dropShadows, animation. y_top is text-only and present ONLY when alignment is 'center' — the unambiguous TOP edge, which is exactly the case where `y` is NOT the top but the vertical CENTRE. To put that position back, send it as `y` with y_anchor:'top'.\n  Examples: ['elements.x','elements.y','elements.width','elements.height'] to read geometry; ['elements.name','elements.start_time','elements.end_time'] for a timing pass; ['voiceover_words'] to sync visuals to narration; ['groups'] to resolve a group id for update_groups; [] with render:{} returns the PNG with no JSON (smallest read); ['elements.textdata','voiceover_words'] to rewrite copy against the VO.\n  Mixing 'elements' with 'elements.<key>' returns whole rows. Use element_ids to choose WHICH rows — that is independent of this."
    • changedInput schema / properties / select / items / enum
      Previous value: -[
      -  "elements",
      -  "voiceover_words",
      -  "groups",
      -  "busy",
      -  "elements.name",
      -  "elements.element_type",
      -  "elements.x",
      -  "elements.y",
      -  "elements.width",
      -  "elements.height",
      -  "elements.start_time",
      -  "elements.end_time",
      -  "elements.rotation",
      -  "elements.keyframes",
      -  "elements.textdata",
      -  "elements.shapedata",
      -  "elements.imagedata",
      -  "elements.videodata",
      -  "elements.zoomdata",
      -  "elements.codedata",
      -  "elements.avatardata",
      -  "elements.parent_id",
      -  "elements.dropShadows"
      -]New value: +[
      +  "elements",
      +  "voiceover_words",
      +  "groups",
      +  "busy",
      +  "elements.name",
      +  "elements.element_type",
      +  "elements.x",
      +  "elements.y",
      +  "elements.width",
      +  "elements.height",
      +  "elements.start_time",
      +  "elements.end_time",
      +  "elements.rotation",
      +  "elements.keyframes",
      +  "elements.textdata",
      +  "elements.shapedata",
      +  "elements.imagedata",
      +  "elements.videodata",
      +  "elements.zoomdata",
      +  "elements.codedata",
      +  "elements.avatardata",
      +  "elements.parent_id",
      +  "elements.dropShadows",
      +  "elements.animation"
      +]
  2. Changed2 schema fields changed
    • changedInput schema / properties / select / description
      Previous value: -"Ask for exactly the JSON you want, GraphQL-style. Omit for everything; pass [] for none.\n  Sections: 'elements' (whole element rows), 'voiceover_words' (per-word VO timings; returns the key of the same name, holding `{word, start, end, punctuated_word}` with start/end in SECONDS — null on a clip with no transcription), 'groups' (group rows: id, name, parent_id, bounds_px, anchor_px, keyframes), 'busy' (generations still writing to this clip, as `[{entity_path, job_type}]` — EMPTY means nothing is pending, which is how you know a voiceover or AI image has landed; it is the same lock that would refuse your write, so a non-empty list also tells you what not to touch yet). Each section returns the key it is named after. Rendering is `render`, not a value here.\n  Per-key: 'elements.<key>' projects element rows to just those keys (id is always kept). Keys: name, element_type, x, y, width, height, start_time, end_time, rotation, keyframes, textdata, shapedata, imagedata, videodata, zoomdata, codedata, parent_id, dropShadows. y_top is text-only and present ONLY when alignment is 'center' — the unambiguous TOP edge, which is exactly the case where `y` is NOT the top but the vertical CENTRE. To put that position back, send it as `y` with y_anchor:'top'.\n  Examples: ['elements.x','elements.y','elements.width','elements.height'] to read geometry; ['elements.name','elements.start_time','elements.end_time'] for a timing pass; ['voiceover_words'] to sync visuals to narration; ['groups'] to resolve a group id for update_groups; [] with render:{} returns the PNG with no JSON (smallest read); ['elements.textdata','voiceover_words'] to rewrite copy against the VO.\n  Mixing 'elements' with 'elements.<key>' returns whole rows. Use element_ids to choose WHICH rows — that is independent of this."New value: +"Ask for exactly the JSON you want, GraphQL-style. Omit for everything; pass [] for none.\n  Sections: 'elements' (whole element rows), 'voiceover_words' (per-word VO timings; returns the key of the same name, holding `{word, start, end, punctuated_word}` with start/end in SECONDS — null on a clip with no transcription), 'groups' (group rows: id, name, parent_id, bounds_px, anchor_px, keyframes), 'busy' (generations still writing to this clip, as `[{entity_path, job_type}]` — EMPTY means nothing is pending, which is how you know a voiceover or AI image has landed; it is the same lock that would refuse your write, so a non-empty list also tells you what not to touch yet). Each section returns the key it is named after. Rendering is `render`, not a value here.\n  Per-key: 'elements.<key>' projects element rows to just those keys (id is always kept). Keys: name, element_type, x, y, width, height, start_time, end_time, rotation, keyframes, textdata, shapedata, imagedata, videodata, zoomdata, codedata, avatardata, parent_id, dropShadows. y_top is text-only and present ONLY when alignment is 'center' — the unambiguous TOP edge, which is exactly the case where `y` is NOT the top but the vertical CENTRE. To put that position back, send it as `y` with y_anchor:'top'.\n  Examples: ['elements.x','elements.y','elements.width','elements.height'] to read geometry; ['elements.name','elements.start_time','elements.end_time'] for a timing pass; ['voiceover_words'] to sync visuals to narration; ['groups'] to resolve a group id for update_groups; [] with render:{} returns the PNG with no JSON (smallest read); ['elements.textdata','voiceover_words'] to rewrite copy against the VO.\n  Mixing 'elements' with 'elements.<key>' returns whole rows. Use element_ids to choose WHICH rows — that is independent of this."
    • changedInput schema / properties / select / items / enum
      Previous value: -[
      -  "elements",
      -  "voiceover_words",
      -  "groups",
      -  "busy",
      -  "elements.name",
      -  "elements.element_type",
      -  "elements.x",
      -  "elements.y",
      -  "elements.width",
      -  "elements.height",
      -  "elements.start_time",
      -  "elements.end_time",
      -  "elements.rotation",
      -  "elements.keyframes",
      -  "elements.textdata",
      -  "elements.shapedata",
      -  "elements.imagedata",
      -  "elements.videodata",
      -  "elements.zoomdata",
      -  "elements.codedata",
      -  "elements.parent_id",
      -  "elements.dropShadows"
      -]New value: +[
      +  "elements",
      +  "voiceover_words",
      +  "groups",
      +  "busy",
      +  "elements.name",
      +  "elements.element_type",
      +  "elements.x",
      +  "elements.y",
      +  "elements.width",
      +  "elements.height",
      +  "elements.start_time",
      +  "elements.end_time",
      +  "elements.rotation",
      +  "elements.keyframes",
      +  "elements.textdata",
      +  "elements.shapedata",
      +  "elements.imagedata",
      +  "elements.videodata",
      +  "elements.zoomdata",
      +  "elements.codedata",
      +  "elements.avatardata",
      +  "elements.parent_id",
      +  "elements.dropShadows"
      +]
  3. Changed2 schema fields changed
    • changedInput schema / properties / select / description
      Previous value: -"Ask for exactly the JSON you want, GraphQL-style. Omit for everything; pass [] for none.\n  Sections: 'elements' (whole element rows), 'voiceover_words' (per-word VO timings; returns the key of the same name, holding `{word, start, end, punctuated_word}` with start/end in SECONDS — null on a clip with no transcription), 'groups' (group rows: id, name, parent_id, bounds_px, anchor_px, keyframes), 'busy' (generations still writing to this clip, as `[{entity_path, job_type}]` — EMPTY means nothing is pending, which is how you know a voiceover or AI image has landed; it is the same lock that would refuse your write, so a non-empty list also tells you what not to touch yet). Each section returns the key it is named after. Rendering is `render`, not a value here.\n  Per-key: 'elements.<key>' projects element rows to just those keys (id is always kept). Keys: name, element_type, x, y, width, height, start_time, end_time, rotation, keyframes, textdata, shapedata, imagedata, videodata, zoomdata, codedata, parent_id. y_top is text-only and present ONLY when alignment is 'center' — the unambiguous TOP edge, which is exactly the case where `y` is NOT the top but the vertical CENTRE. To put that position back, send it as `y` with y_anchor:'top'.\n  Examples: ['elements.x','elements.y','elements.width','elements.height'] to read geometry; ['elements.name','elements.start_time','elements.end_time'] for a timing pass; ['voiceover_words'] to sync visuals to narration; ['groups'] to resolve a group id for update_groups; [] with render:{} returns the PNG with no JSON (smallest read); ['elements.textdata','voiceover_words'] to rewrite copy against the VO.\n  Mixing 'elements' with 'elements.<key>' returns whole rows. Use element_ids to choose WHICH rows — that is independent of this."New value: +"Ask for exactly the JSON you want, GraphQL-style. Omit for everything; pass [] for none.\n  Sections: 'elements' (whole element rows), 'voiceover_words' (per-word VO timings; returns the key of the same name, holding `{word, start, end, punctuated_word}` with start/end in SECONDS — null on a clip with no transcription), 'groups' (group rows: id, name, parent_id, bounds_px, anchor_px, keyframes), 'busy' (generations still writing to this clip, as `[{entity_path, job_type}]` — EMPTY means nothing is pending, which is how you know a voiceover or AI image has landed; it is the same lock that would refuse your write, so a non-empty list also tells you what not to touch yet). Each section returns the key it is named after. Rendering is `render`, not a value here.\n  Per-key: 'elements.<key>' projects element rows to just those keys (id is always kept). Keys: name, element_type, x, y, width, height, start_time, end_time, rotation, keyframes, textdata, shapedata, imagedata, videodata, zoomdata, codedata, parent_id, dropShadows. y_top is text-only and present ONLY when alignment is 'center' — the unambiguous TOP edge, which is exactly the case where `y` is NOT the top but the vertical CENTRE. To put that position back, send it as `y` with y_anchor:'top'.\n  Examples: ['elements.x','elements.y','elements.width','elements.height'] to read geometry; ['elements.name','elements.start_time','elements.end_time'] for a timing pass; ['voiceover_words'] to sync visuals to narration; ['groups'] to resolve a group id for update_groups; [] with render:{} returns the PNG with no JSON (smallest read); ['elements.textdata','voiceover_words'] to rewrite copy against the VO.\n  Mixing 'elements' with 'elements.<key>' returns whole rows. Use element_ids to choose WHICH rows — that is independent of this."
    • changedInput schema / properties / select / items / enum
      Previous value: -[
      -  "elements",
      -  "voiceover_words",
      -  "groups",
      -  "busy",
      -  "elements.name",
      -  "elements.element_type",
      -  "elements.x",
      -  "elements.y",
      -  "elements.width",
      -  "elements.height",
      -  "elements.start_time",
      -  "elements.end_time",
      -  "elements.rotation",
      -  "elements.keyframes",
      -  "elements.textdata",
      -  "elements.shapedata",
      -  "elements.imagedata",
      -  "elements.videodata",
      -  "elements.zoomdata",
      -  "elements.codedata",
      -  "elements.parent_id"
      -]New value: +[
      +  "elements",
      +  "voiceover_words",
      +  "groups",
      +  "busy",
      +  "elements.name",
      +  "elements.element_type",
      +  "elements.x",
      +  "elements.y",
      +  "elements.width",
      +  "elements.height",
      +  "elements.start_time",
      +  "elements.end_time",
      +  "elements.rotation",
      +  "elements.keyframes",
      +  "elements.textdata",
      +  "elements.shapedata",
      +  "elements.imagedata",
      +  "elements.videodata",
      +  "elements.zoomdata",
      +  "elements.codedata",
      +  "elements.parent_id",
      +  "elements.dropShadows"
      +]
  4. Changed4 schema fields changed
    • removedInput schema / properties / context
      Removed value: -{
      -  "description": "Explain in 15-25 words, in third person, why this tool is called and how it supports the user's goal. For analytics only. You MUST describe only the abstract purpose of the tool call. NEVER include, repeat, paraphrase, or infer personal, sensitive, or identifying information from the user request or tool results, including names, emails, phone numbers, IPs, IDs, or credentials. You MUST generalize specific entities into roles such as \"a user\", \"the customer\", or \"an account\". Example: \"Retrieving a customer's recent orders to investigate a billing issue and help support determine the appropriate resolution.\"",
      -  "type": "string"
      -}
    • removedInput schema / properties / conversation_id
      Removed value: -{
      -  "description": "Echo the conversation_id from the server's previous response. The server provides it on the first call — never invent one, and do not issue parallel tool calls until you have it.",
      -  "type": "string"
      -}
    • removedInput schema / properties / llm_model
      Removed value: -{
      -  "description": "The exact model identifier you (the assistant) are running as, taken from your system prompt or environment (e.g. \"claude-opus-4-8\", \"gpt-5.2\"). Used for analytics only. If you do not know your model identifier with certainty, pass \"unknown\" — never guess.",
      -  "type": "string"
      -}
    • changedInput schema / required
      Previous value: -[
      -  "project_id",
      -  "clip_index",
      -  "context",
      -  "llm_model"
      -]New value: +[
      +  "project_id",
      +  "clip_index"
      +]
  5. Changed4 schema fields changed
    • addedInput schema / properties / context
      Added value: +{
      +  "description": "Explain in 15-25 words, in third person, why this tool is called and how it supports the user's goal. For analytics only. You MUST describe only the abstract purpose of the tool call. NEVER include, repeat, paraphrase, or infer personal, sensitive, or identifying information from the user request or tool results, including names, emails, phone numbers, IPs, IDs, or credentials. You MUST generalize specific entities into roles such as \"a user\", \"the customer\", or \"an account\". Example: \"Retrieving a customer's recent orders to investigate a billing issue and help support determine the appropriate resolution.\"",
      +  "type": "string"
      +}
    • addedInput schema / properties / conversation_id
      Added value: +{
      +  "description": "Echo the conversation_id from the server's previous response. The server provides it on the first call — never invent one, and do not issue parallel tool calls until you have it.",
      +  "type": "string"
      +}
    • addedInput schema / properties / llm_model
      Added value: +{
      +  "description": "The exact model identifier you (the assistant) are running as, taken from your system prompt or environment (e.g. \"claude-opus-4-8\", \"gpt-5.2\"). Used for analytics only. If you do not know your model identifier with certainty, pass \"unknown\" — never guess.",
      +  "type": "string"
      +}
    • changedInput schema / required
      Previous value: -[
      -  "project_id",
      -  "clip_index"
      -]New value: +[
      +  "project_id",
      +  "clip_index",
      +  "context",
      +  "llm_model"
      +]
  6. Changed5 schema fields changed
    • changedInput schema / $schema
      Previous value: -"http://json-schema.org/draft-07/schema#"New value: +"https://json-schema.org/draft/2020-12/schema"
    • removedInput schema / additionalProperties
      Removed value: -false
    • addedInput schema / properties / clip_index / maximum
      Added value: +9007199254740991
    • changedInput schema / properties / select / description
      Previous value: -"Ask for exactly the JSON you want, GraphQL-style. Omit for everything; pass [] for none.\n  Sections: 'elements' (whole element rows), 'voiceover_words' (per-word VO timings; returns the key of the same name, holding `{word, start, end, punctuated_word}` with start/end in SECONDS — null on a clip with no transcription), 'groups' (group rows: id, name, parent_id, bounds_px, anchor_px, keyframes). Each section returns the key it is named after. Rendering is `render`, not a value here.\n  Per-key: 'elements.<key>' projects element rows to just those keys (id is always kept). Keys: name, element_type, x, y, width, height, start_time, end_time, rotation, keyframes, textdata, shapedata, imagedata, videodata, zoomdata, codedata, parent_id.\n  Examples: ['elements.x','elements.y','elements.width','elements.height'] to read geometry; ['elements.name','elements.start_time','elements.end_time'] for a timing pass; ['voiceover_words'] to sync visuals to narration; ['groups'] to resolve a group id for update_groups; [] with render:{} returns the PNG with no JSON (smallest read); ['elements.textdata','voiceover_words'] to rewrite copy against the VO.\n  Mixing 'elements' with 'elements.<key>' returns whole rows. Use element_ids to choose WHICH rows — that is independent of this."New value: +"Ask for exactly the JSON you want, GraphQL-style. Omit for everything; pass [] for none.\n  Sections: 'elements' (whole element rows), 'voiceover_words' (per-word VO timings; returns the key of the same name, holding `{word, start, end, punctuated_word}` with start/end in SECONDS — null on a clip with no transcription), 'groups' (group rows: id, name, parent_id, bounds_px, anchor_px, keyframes), 'busy' (generations still writing to this clip, as `[{entity_path, job_type}]` — EMPTY means nothing is pending, which is how you know a voiceover or AI image has landed; it is the same lock that would refuse your write, so a non-empty list also tells you what not to touch yet). Each section returns the key it is named after. Rendering is `render`, not a value here.\n  Per-key: 'elements.<key>' projects element rows to just those keys (id is always kept). Keys: name, element_type, x, y, width, height, start_time, end_time, rotation, keyframes, textdata, shapedata, imagedata, videodata, zoomdata, codedata, parent_id. y_top is text-only and present ONLY when alignment is 'center' — the unambiguous TOP edge, which is exactly the case where `y` is NOT the top but the vertical CENTRE. To put that position back, send it as `y` with y_anchor:'top'.\n  Examples: ['elements.x','elements.y','elements.width','elements.height'] to read geometry; ['elements.name','elements.start_time','elements.end_time'] for a timing pass; ['voiceover_words'] to sync visuals to narration; ['groups'] to resolve a group id for update_groups; [] with render:{} returns the PNG with no JSON (smallest read); ['elements.textdata','voiceover_words'] to rewrite copy against the VO.\n  Mixing 'elements' with 'elements.<key>' returns whole rows. Use element_ids to choose WHICH rows — that is independent of this."
    • changedInput schema / properties / select / items / enum
      Previous value: -[
      -  "elements",
      -  "voiceover_words",
      -  "groups",
      -  "elements.name",
      -  "elements.element_type",
      -  "elements.x",
      -  "elements.y",
      -  "elements.width",
      -  "elements.height",
      -  "elements.start_time",
      -  "elements.end_time",
      -  "elements.rotation",
      -  "elements.keyframes",
      -  "elements.textdata",
      -  "elements.shapedata",
      -  "elements.imagedata",
      -  "elements.videodata",
      -  "elements.zoomdata",
      -  "elements.codedata",
      -  "elements.parent_id"
      -]New value: +[
      +  "elements",
      +  "voiceover_words",
      +  "groups",
      +  "busy",
      +  "elements.name",
      +  "elements.element_type",
      +  "elements.x",
      +  "elements.y",
      +  "elements.width",
      +  "elements.height",
      +  "elements.start_time",
      +  "elements.end_time",
      +  "elements.rotation",
      +  "elements.keyframes",
      +  "elements.textdata",
      +  "elements.shapedata",
      +  "elements.imagedata",
      +  "elements.videodata",
      +  "elements.zoomdata",
      +  "elements.codedata",
      +  "elements.parent_id"
      +]
  7. Changed9 schema fields changed
    • changedInput schema / properties / render / description
      Previous value: -"Render a PNG of the frame. Presence of this object IS the request to render — omit it and nothing is rendered. `{}` renders at t=0. Independent of `select`, which only shapes the JSON: pair `select: []` with `render: {}` for the PNG alone (smallest read). CHECKING YOUR WORK: pass a mid-clip `timestamp`, not the t=0 default — text and image elements have entry animations (a ~0.4s slide/fade by default), so at t=0 they have not arrived yet and a correct edit renders as an empty frame. Shapes have no entry animation and do show at t=0, which makes a t=0 render especially misleading: some elements appear and others don't."New value: +"Render the clip as an image. Presence of this object IS the request to render — omit it and nothing is rendered. `{}` renders at t=0. Independent of `select`, which only shapes the JSON: pair `select: []` with `render: {}` for the PNG alone (smallest read). CHECKING YOUR WORK: pass `timestamps` with SEVERAL mid-clip moments, not the t=0 default — text and image elements have entry animations (a ~0.4s slide/fade by default), so at t=0 they have not arrived yet and a correct edit renders as an empty frame, while shapes have no entry animation and do show at t=0. That mix is what makes a single t=0 render actively misleading: some elements appear and others don't. A list comes back as one grid for about a quarter of the tokens of the same frames separately, so checking several moments is the cheap option, not the expensive one. `animations: false` draws everything settled if you would rather not pick moments at all."
    • addedInput schema / properties / render / properties / animations
      Added value: +{
      +  "description": "Whether entry/exit animations play at this timestamp. Default true — the frame as the video will show it, which is what you want to judge motion. Pass false to draw every element settled at its final position and full opacity: use that to check COMPOSITION, because with staggered starts and short windows there may be no single timestamp at which nothing is mid-flight, and an element part-way through a slide reads as mis-positioned when it is fine.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / render / properties / layout
      Added value: +{
      +  "description": "Only meaningful when `timestamps` holds several. 'sheet' (default) tiles them into one image; 'separate' returns them full-size, which costs ~4x more — use it only when a grid cell is too small to judge.",
      +  "enum": [
      +    "sheet",
      +    "separate"
      +  ],
      +  "type": "string"
      +}
    • addedInput schema / properties / render / properties / max_width
      Added value: +{
      +  "description": "Width in px of the IMAGE you get back — for a grid that is the whole grid, not each cell, so a cell is roughly a third of it. Default 960 for a single frame, 1280 for a grid. An image costs roughly (w×h)/750 tokens, so 1280 is ~1229 and 960 ~691; above 1568 nothing is gained because the model downscales to that anyway. The default is legible for this design system's type scale — a 40px canvas caption lands at ~20px. Raise it only to read genuinely small print, such as UI text inside a pasted screenshot.",
      +  "maximum": 1568,
      +  "minimum": 320,
      +  "type": "integer"
      +}
    • changedInput schema / properties / render / properties / save / description
      Previous value: -"Also upload the rendered PNG to S3 and return presigned_url, which you can pass as source_url to update_clueprint (files=[{path, source_url}])."New value: +"Also upload the rendered image to S3 and return presigned_url, which you can pass as source_url to update_clueprint (files=[{path, source_url}]). Uploads ONE image, so it works with a single frame or a grid but not with layout:'separate' — there is one URL and several frames."
    • removedInput schema / properties / render / properties / timestamp
      Removed value: -{
      -  "description": "The moment within the clip to render, in seconds (default 0).",
      -  "minimum": 0,
      -  "type": "number"
      -}
    • addedInput schema / properties / render / properties / timestamps
      Added value: +{
      +  "anyOf": [
      +    {
      +      "minimum": 0,
      +      "type": "number"
      +    },
      +    {
      +      "items": {
      +        "minimum": 0,
      +        "type": "number"
      +      },
      +      "maxItems": 15,
      +      "minItems": 1,
      +      "type": "array"
      +    }
      +  ],
      +  "description": "Which moment(s) of the clip to render, in seconds (default 0). Pass several — [0.5, 2, 4] — and they come back as one labelled grid costing about a quarter of what those frames cost individually. Prefer several whenever you are checking rather than reading: entry animations mean no single moment shows everything, so one frame is usually the wrong question. A grid cell is a third of the width, so for small print pass a single number. Max 15; duplicates dropped, sorted into time order."
      +}
    • changedInput schema / properties / select / description
      Previous value: -"Ask for exactly the JSON you want, GraphQL-style. Omit for everything; pass [] for none.\n  Sections: 'elements' (whole element rows), 'words' (per-word VO timings). Rendering is `render`, not a value here.\n  Per-key: 'elements.<key>' projects element rows to just those keys (id is always kept). Keys: name, geo, x, y, width, height, start_time, end_time, rotation, keyframes, textdata, shapedata, imagedata, videodata, zoomdata, codedata, parent_id.\n  Examples: ['elements.x','elements.y','elements.width','elements.height'] to read geometry; ['elements.name','elements.start_time','elements.end_time'] for a timing pass; ['words'] to sync visuals to narration; [] with render:{} returns the PNG with no JSON (smallest read); ['elements.textdata','words'] to rewrite copy against the VO.\n  Mixing 'elements' with 'elements.<key>' returns whole rows. Use element_ids to choose WHICH rows — that is independent of this."New value: +"Ask for exactly the JSON you want, GraphQL-style. Omit for everything; pass [] for none.\n  Sections: 'elements' (whole element rows), 'voiceover_words' (per-word VO timings; returns the key of the same name, holding `{word, start, end, punctuated_word}` with start/end in SECONDS — null on a clip with no transcription), 'groups' (group rows: id, name, parent_id, bounds_px, anchor_px, keyframes). Each section returns the key it is named after. Rendering is `render`, not a value here.\n  Per-key: 'elements.<key>' projects element rows to just those keys (id is always kept). Keys: name, element_type, x, y, width, height, start_time, end_time, rotation, keyframes, textdata, shapedata, imagedata, videodata, zoomdata, codedata, parent_id.\n  Examples: ['elements.x','elements.y','elements.width','elements.height'] to read geometry; ['elements.name','elements.start_time','elements.end_time'] for a timing pass; ['voiceover_words'] to sync visuals to narration; ['groups'] to resolve a group id for update_groups; [] with render:{} returns the PNG with no JSON (smallest read); ['elements.textdata','voiceover_words'] to rewrite copy against the VO.\n  Mixing 'elements' with 'elements.<key>' returns whole rows. Use element_ids to choose WHICH rows — that is independent of this."
    • changedInput schema / properties / select / items / enum
      Previous value: -[
      -  "elements",
      -  "words",
      -  "elements.name",
      -  "elements.geo",
      -  "elements.x",
      -  "elements.y",
      -  "elements.width",
      -  "elements.height",
      -  "elements.start_time",
      -  "elements.end_time",
      -  "elements.rotation",
      -  "elements.keyframes",
      -  "elements.textdata",
      -  "elements.shapedata",
      -  "elements.imagedata",
      -  "elements.videodata",
      -  "elements.zoomdata",
      -  "elements.codedata",
      -  "elements.parent_id"
      -]New value: +[
      +  "elements",
      +  "voiceover_words",
      +  "groups",
      +  "elements.name",
      +  "elements.element_type",
      +  "elements.x",
      +  "elements.y",
      +  "elements.width",
      +  "elements.height",
      +  "elements.start_time",
      +  "elements.end_time",
      +  "elements.rotation",
      +  "elements.keyframes",
      +  "elements.textdata",
      +  "elements.shapedata",
      +  "elements.imagedata",
      +  "elements.videodata",
      +  "elements.zoomdata",
      +  "elements.codedata",
      +  "elements.parent_id"
      +]
  8. First observed

TDQS

A4.7/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly/destructive, but the description goes well beyond them: token cost of renders, the 'single t=0 render is actively misleading because of entry animations' trap, the ~4x cost of layout:'separate', the busy-lock semantics, and that a render of one animation returns `warnings`. This is exactly the behavioral context annotations cannot carry.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Well front-loaded and organized with example blocks, but far longer than needed: large stretches duplicate the input schema's own parameter descriptions almost verbatim, and the render option list is enumerated twice (description and schema). Structure is good; restraint is not.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 5-parameter tool with deep nested objects, two required params and no output schema, the description covers read shape, cost model, render semantics, animation manifest vocabulary and the warnings/behind/dependencyUpdates fields an agent must interpret. Nothing needed to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds meaning the schema does not: worked `select` combinations per use case, the select/render separation ('select shapes the JSON, render produces a PNG'), the element_ids-vs-select independence, and the keyframes round-trip note. Some of it restates schema prose, which keeps it below 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Read one clip') and enumerates what a read returns (elements with canvas-pixel geometry, voiceover, background, transition), plus the `render` add-on. An agent can distinguish this from get_project, get_element_schema and the update_* siblings without opening a schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use guidance throughout: 'omit select -> everything; fine for a first look, expensive to repeat', 'repeated full reads are the main way a long session runs out of context', and the CHECKING YOUR WORK note steering callers to multi-timestamp renders over the t=0 default. It also routes to siblings (update_groups, update_elements, get_project seams, edit_animation dry runs).

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.