Skip to content

studio — full tool reference

Studio projects, compositions, slides, versions, comments, templates and themes.

Endpointhttps://studio.mcp.devfellowship.com/mcp
Packagepackages/dfl-mcp-studio
Tools72
ToolDescription
create_studio_projectCreate a new Lesson Studio project in the lesson_studio schema, plus a “Default” composition. Returns the project_id and the default composition_id (attach slides to a composition via create_slide).
list_studio_projectsList Lesson Studio projects owned by the caller (RLS-scoped), most recently updated first.
delete_studio_projectDelete a Lesson Studio project. Its compositions, slides, slide_elements and slide_media cascade. RLS-scoped: only the caller’s own projects can be deleted.
update_studio_projectUpdate a project’s title, description, thumbnail, orientation, active theme (settings.theme_id), burned-in captions (settings.captions_*) or export watermark (settings.watermark). For a 9:16 reel, pass captions {enabled: true, position: “top”} and watermark {enabled: true} before start_composition_export. Camera defaults are set_project_camera’s job, not this tool’s. theme_id is checked against list_themes when the registry is reachable; an unknown id is rejected. A settings write (theme_id, captions, watermark) is guarded on updated_at (it reads settings first to merge; keys you leave out keep their stored value); a change to the other fields overwrites only the columns passed. Changing orientation does NOT reshape camera boxes — it warns and points at set_project_camera.
create_compositionCreate a composition (lesson-level grouping / “frame”) under a Lesson Studio project. Slides attach to a composition.
list_compositionsList the compositions of a Lesson Studio project, ordered by order_index.
update_compositionUpdate a composition title and/or order_index.
delete_compositionDelete a composition. Its slides must be removed or reassigned first (slides.composition_id is NOT NULL).
create_slideCreate a slide under a composition (mirrors the Lesson Studio editor insert shape so it is valid + editable in the UI). Optional template_data.animation animates the slide. Preset ‘steps-reveal’ shows list items one at a time; use it when the narration walks through steps, on a template whose items carry data-anim-target=“step”. Example: {preset:‘steps-reveal’,version:1,beat_source:‘manual’,beats:[{kind:‘reveal’,target:‘step’,index:0,start_ms:600,duration_ms:300}]}. Sort beats by start_ms. Every beat must end before the slide duration_ms. The response can list warnings. sfx: [{sound, at_ms, volume?}] — see list_sound_effects.
create_image_slideCreate a slide that displays a single image filling the entire slide canvas, using the fullscreen-image template. Use this to bring your own finished slide images (e.g. an image hosted on S3, or a pre-rendered slide exported from another tool) instead of reusing the built-in content templates. The fit option controls how the image fills the canvas: cover (default) crops to fill, contain letterboxes to show the whole image.
list_slidesList the slides of a composition, ordered by order_index. Soft-deleted slides are excluded by default; pass include_deleted:true to return all (including soft-deleted).
update_slideUpdate a slide. template_data replaces the slots; an omitted slot is removed. Studio keys (not slots): narration_notes, animation, background_override, camera, camera_box, capture, camera_box_custom, watermark, webcam_layout, pointer_track, is_cover, sfx, framing, caption_cues, clips, style_overrides, slide_name, caption_cues_source, caption_language, camera_segments, content_over_camera, caption_position, camera_focus, insert. An omitted Studio key is kept, except animation: a template_id change removes it unless you send it. Remove one via clear_template_data_keys, only when the user asks. narration_notes and pointer_track are user work and cannot be restored. The response lists removed_template_data_keys. A new background sets background_override = true. To show the template background again, send clear_template_data_keys: [‘background_override’] and no background field. A sent animation is checked as the create_slide description says, against the slide duration_ms. A new duration_ms is checked against the stored animation. The response can list warnings. sfx: [{sound, at_ms, volume?}] — see list_sound_effects.
delete_slideDelete a slide. By default this is a SOFT delete (recoverable via restore_slide; slide_elements/slide_media are kept). Pass hard:true for a permanent delete (slide_elements + slide_media cascade).
restore_slideRestore a soft-deleted slide (clears deleted_at/deleted_by so it reappears in list_slides). Only works on slides that were soft-deleted via delete_slide; a hard-deleted slide is gone permanently.
list_animation_presetsThe slide animation presets set_slide_animation can apply, with the template_data slots each one reads. Pass template_id to also learn which of them that template can actually run: the answer is read from the live template markup (data-anim-target), so it cannot go stale. A preset whose slots the slide does not carry produces no beats — that is what requires describes.
set_slide_animationAnimate a slide with one of the presets from list_animation_presets. With no beats, the timings are computed from the slide’s own template_data and duration_ms — the same defaults the Lesson Studio editor applies, already fitted to the slide length and valid at 24, 30 and 60 fps. Pass beats only for a timing the defaults do not express. “zoom-focus” is the one preset with no template slot — it works on any slide, pushing in and holding until a later zoom beat frames back out. A “zoom” beat also COMPOSES with every other preset (add one to the “beats” array of a steps-reveal/chat-typing/highlight/image-enter spec): it drives the synthetic “slide” target no template markup ever declares, so it never collides with that preset’s own beats. Writes template_data.animation and leaves every other template_data key untouched. To remove an animation, call update_slide with clear_template_data_keys: [‘animation’].
search_slide_imagesSearch the stock photo library for an image to put on a slide, and get back candidate URLs. Use it to fill an image-type template slot without a human opening the editor: pick a result and write its url with update_slide (template_data.image) or create_image_slide (image_url). This tool only reads — it never touches a slide. Match orientation to the project canvas so the crop is not fighting the layout. Results carry photographer and pexels_url: the licence asks for that credit, so keep it with the image.
set_cover_slideMark a slide as the deck’s cover (template_data.is_cover), or unmark it (is_cover: false). The cover is used for the video thumbnail via capture_cover_slide, but dfl-render EXCLUDES it from the rendered video, its slide count, and the 01/09 deck index. Setting is_cover: true clears the flag from every other slide in the same composition first — a deck has at most one cover. Preserves every other template_data key. To set the composition’s thumbnail after capturing it, chain the result into whichever tool writes projects.thumbnail_url (this tool never writes it).
list_slide_beatsList the beats of a slide’s template_data.animation, each tagged with the beat_index add_slide_beat/update_slide_beat/remove_slide_beat address. Returns an empty beats array (not an error) when the slide carries no animation.
add_slide_beatInsert one beat into a slide’s template_data.animation.beats. The slide must already carry an animation (create one with set_slide_animation first). Beats must stay sorted by start_ms; a beat or at_index that breaks the order is rejected with the same validator set_slide_animation uses. Marks beat_source “manual”. Returns the new beat_index.
update_slide_beatReplace one beat of a slide’s template_data.animation.beats, addressed by beat_index (from list_slide_beats). beat replaces the ENTIRE beat, not just the fields you pass — send every field the beat needs, same shape set_slide_animation’s “beats” entries take. Beats must stay sorted by start_ms after the replacement. Marks beat_source “manual”.
remove_slide_beatRemove one beat from a slide’s template_data.animation.beats, addressed by beat_index (from list_slide_beats). A spec needs at least one beat, so removing the only beat is rejected — use update_slide with clear_template_data_keys: [‘animation’] to drop the animation entirely. Marks beat_source “manual”.
create_slide_elementCreate a free-canvas box on a slide: a text box, an image, or a shape drawn on top of the slide’s template. position_x/position_y/width/height/rotation are DESIGN pixels — the canvas is 1280x720 for a landscape project and 720x1280 for a portrait one (the parent project’s own orientation, not an argument here). A box entirely outside that canvas is rejected; a box that only partly overlaps it is allowed (that is a normal partial-off-canvas placement, not an error). content is validated by type: text.text is at most 2000 characters; image.url must be an http(s) URL; every color (text.color, shape.fill, shape.stroke, …) must be a hex color (#rgb, #rrggbb, #rrggbbaa) or an rgba()/rgb() function; an unknown content key is rejected. A ‘text’ box may omit content fields — missing ones are filled from the Studio’s own default text style, so { type: ‘text’, content: { text: ‘Hello’ } } is enough for a fully-styled box. ‘image’ and ‘shape’ boxes must supply every field their type needs. The response carries animation_index: the box’s position among the slide’s elements, ordered by creation time — THIS is the index a set_slide_animation “element-reveal” beat ({ kind: “reveal”, target: “element”, index }) must use to animate THIS box, because that index is the box’s position in the stored array, never its z_index or the order it was drawn on screen. Example: create_slide_element({ slide_id, type: “text”, content: { text: “Welcome” }, position_x: 100, position_y: 80, width: 600, height: 120 }) → { element: {…}, animation_index: 0 }.
list_slide_elementsList a slide’s free-canvas boxes (text/image/shape), ordered by creation time. Each element carries animation_index: its position in this order, and the exact index a reveal beat with target: "element" must reference to animate it — never the box’s z_index or draw order. Example response: { elements: [{ id, type: “text”, content: {…}, position_x, position_y, width, height, rotation, opacity, z_index, animation_index: 0 }, …] }.
update_slide_elementUpdate a free-canvas box’s content and/or geometry (position_x/position_y/width/height/rotation/opacity/z_index). Every field is optional — send only what changes. content is a PARTIAL patch: it is merged over the box’s stored content and the merged object is re-validated against its type’s rules (same rules as create_slide_element — text max 2000 chars, image url must be http(s), colors must be hex or rgba()/rgb(), unknown keys rejected). Geometry is design pixels, same canvas rule as create_slide_element: a box moved/resized fully outside the canvas is rejected. The box’s type and its animation_index (set by create order) cannot be changed here. Example: update_slide_element({ id, content: { text: “Updated headline” }, position_x: 140 }).
delete_slide_elementDelete a free-canvas box and repair the slide’s animation in the same call. A reveal beat (target: "element") addresses a box by its position among the slide’s elements (animation_index), so removing a box shifts every later box’s beat index down by one — this tool does that shift automatically: the removed box’s own beat is dropped, later beats are reindexed keeping their effect/focus, and if no element beat survives template_data.animation is removed entirely (an animation with zero beats is not valid). The response reports removed_beat_count, shifted_beat_count and animation_cleared so the caller knows exactly what changed.
get_cameraRead the camera box + visibility for a project (project_id) and optionally one of its slides (slide_id). Reports the RAW override at each level (project default, slide override) plus the RESOLVED value that actually applies, following the same precedence dfl-render uses (slide, then project, then the built-in default). Pass slide_id alone to also resolve the project it belongs to.
set_project_cameraSet the project-wide camera default: aspect (16:9 or 1:1), box (preset size+corner or custom fractions), and default visibility (off/overlay). Applies to every slide that does not override its own camera via set_slide_camera. Changing aspect with no box in the same call reshapes the current project box (and every slide’s own camera_box override) to fit the new aspect, mirroring the Lesson Studio editor. Guarded on updated_at. focus {x, y} (0..1 of the camera frame) sets the project default crop point (settings.camera_focus); a slide focus (set_slide_camera) wins.
set_slide_cameraOverride this slide’s camera box and/or visibility, on top of the project default set_project_camera sets. box resolves against the parent project’s orientation and camera aspect. clear_box removes the box override (the slide follows the project box again); clear_visibility removes the visibility override. segments limits WHEN the corner (picture-in-picture) camera is drawn — refused on a fullscreen-webcam slide: a list of { start_ms, end_ms } windows in slide time (whole ms, sorted, non-overlapping, within the slide duration, 1-50 of them); the voice keeps playing outside them. clear_segments shows the camera for the whole slide again. To hide it on the whole slide use visibility: ‘off’. focus {x, y} (0..1 of the camera frame) moves the crop of the camera off the centre, e.g. {x: 0.3, y: 0.5} for a face left of centre in a 16:9 take on a portrait slide; clear_focus goes back to the project focus / the centre. Preserves every other template_data key. Guarded on updated_at.
list_sound_effectsThe sound-effect catalog available to template_data.sfx / set_slide_sfx: every valid sound key, its category, use-case hint and duration. Read-only, no database access. Pass category to filter.
set_slide_sfxWrite this slide’s sound effects (template_data.sfx). Each entry is {sound, at_ms, volume?} — sound is a catalog key from list_sound_effects, at_ms must start before the slide duration_ms. mode “replace” (default) discards the stored list; “append” adds to it. An empty array with “replace” removes the key. Preserves every other template_data key. Guarded on updated_at.
set_slide_captionsReplace a slide’s caption cues (template_data.caption_cues). Cues use the recording’s SOURCE clock in ms (the file, not the slide), sorted and non-overlapping, max 2000. source_stamp ‘current’ (default) stamps caption_cues_source with the slide’s recording URL, so the Studio editor does not re-transcribe and overwrite them; ‘none’ removes the stamp (the editor re-transcribes). Keeps every other template_data key. Guarded on updated_at.
edit_caption_cueEdit ONE cue of a slide’s caption_cues by its 0-based index: new text and/or startMs/endMs (source clock, ms). The whole list must stay sorted and non-overlapping. Keeps the caption_cues_source stamp and every other key. Guarded on updated_at.
set_slide_layoutApply a camera/content layout preset to one slide. ‘fullscreen-camera’: the webcam fills the frame, no slide content. ‘overlay-top’: webcam fullscreen, slide content drawn OVER it in the top half (content_over_camera). ‘overlay-full’: webcam fullscreen, slide content drawn OVER it on the WHOLE canvas (no safe band, scale 1) — for a full-canvas image. ‘split-top-image’: slide content on top, full-width camera band at the bottom (portrait only). The band crop keeps the FACE: it finds the face in the slide’s recording and writes camera_focus there (else it keeps the upper part of the frame); an existing camera_focus is kept. On an image-only slide (fullscreen-image, no free elements) it trims the image’s flat margins and contains the drawn area in the top band (content_zoom/offset; the trim is recorded in image_trim); a slide with text keeps the plain band fit. ‘background-pip’: slide content fullscreen, small camera bottom-right. Resolves boxes against the project orientation and camera aspect. One write, guarded on updated_at; returns the written and removed keys. Keeps every other template_data key. caption_position (with or without a preset) sets this slide’s caption placement, e.g. ‘over-camera’ on a split slide so the captions do not cover the image.
transcribe_mediaTranscribe a video/audio (URL or media_id) with dfl-render, optionally translated. Returns the text and caption cues {text, startMs, endMs} on the source clock. Waits up to ~45 s; if still running it returns job_id — then call get_media_transcription. Writes nothing; attach_slide_recording transcribes on its own.
get_media_transcriptionRead a transcription job that transcribe_media started: its status while it runs, else the text, cues and translation.
attach_slide_recordingAttach a raw recording (URL or media_id) to a slide: writes its slide_media row (one per slide), sets the slide duration to the trim window, starts the server transcode, and writes captions — transcribed by dfl-render (optionally translated) or the cues you pass — stamped so the editor keeps them. Waits up to ~45 s; if the transcription still runs, it returns transcription_job_id: then call get_slide_recording. WARNING: an open Lesson Studio tab on this project can overwrite the row on autosave; close it first.
get_slide_recordingRead a slide’s recording: source URL, trim window, transcode_status (export needs ‘done’; ‘processing’ still runs) and captions. With transcription_job_id (from attach_slide_recording) it finishes the attach once the job is done: fills an unknown length and writes the stamped captions, unless the slide already has captions for this recording.
split_recording_across_slidesSpread ONE raw take over several slides. mode window (default): every slide points at the same file with its own trim window [start_ms, end_ms), keep ranges become template_data.clips, and ONE transcription of the whole file gives each slide ONLY its own cues (a cue goes to the slide whose window, or keep range, holds its midpoint, clamped to it). mode cut: dfl-render cuts real files (/media/cut, optional fit) and each slide gets its part, with its own cues shifted onto it. create_slides appends new slides for parts without slide_id. Waits ~45 s; if a job still runs it returns job ids to resume with. An open Studio tab on the project can overwrite the rows on autosave.
import_media_from_urlImport a clip from a public video URL (e.g. a YouTube/X post) through dfl-render: download, optional [start_s, end_s) cut, optional portrait fit, transcription and translation. register_media makes it a public.media row; attach_to_slide_id attaches it to a slide with its (translated) cues, with no second transcription. as_insert (with attach_to_slide_id) makes it an INSERT slide: the clip on its own slide, contained, no camera treatment, optional insert_label. Waits ~45 s; else returns job_id — then call get_media_import. Use only media you may reuse.
get_media_importRead an import_media_from_url job. While it runs, its status. When done, it does the same finish: register_media and/or attach_to_slide_id (pass them again). Safe to call again.
set_slide_insertMake a slide an INSERT slide: an inserted video on its own slide (the video cuts from the camera to the clip, then back). The slide recording plays as a screen recording — contained, no camera box, no camera focus, no face crop — and the camera keys go. label (e.g. “@rohanpaul_ai”) adds a name label box under the clip, drawn above the video in the export; label null removes it. The slide needs a recording (import_media_from_url with as_insert does all of this in one call). Captions stay as they are.
create_project_from_videoTurn one raw take into a new Lesson Studio project in one call: creates the project (portrait by default, no project camera box) with burned-in caption settings, a fullscreen-camera slide per part (default: one slide, the whole take), attaches the recording, transcribes (optionally translates) and writes stamped captions. parts has the split_recording_across_slides shape. Returns project, composition and slide ids; if a job still runs it returns the ids to finish with get_slide_recording or split_recording_across_slides.
set_project_glossarySet a Lesson Studio project’s caption glossary: the canonical spellings (names, tickers — ‘Bessent’, ‘USDC’) that the server transcription applies to the captions (a whole-word near match becomes the term; timings never change). transcribe_media (with project_id), attach_slide_recording, split_recording_across_slides and create_project_from_video use it by default. Replaces the list; terms: [] clears it. Stored in projects.settings.caption_glossary; guarded on updated_at.
upload_slide_imageUpload an image (base64 PNG, JPEG, WebP or GIF, at most 6 MB; the type is read from the bytes, SVG is refused) to DFL media as the caller, PUBLIC (the editor and the export load slide images by URL). Returns the url. With slide_id it also places the image as an image box on that slide: by default the WHOLE canvas (720x1280 portrait, 1280x720 landscape) with fit ‘contain’, above the other boxes — pair it with set_slide_layout ‘overlay-full’ for a full-canvas overlay over a fullscreen camera. box {x,y,width,height} in design pixels places it elsewhere. Use it in place of an upload outside the Studio.
list_project_versionsList the versions of a Lesson Studio project, ordered by version_number descending (current/highest first).
bump_project_versionCreate a new version of a Lesson Studio project (version_number = max existing + 1, starting at 1). Subsequent comments are stamped to this new version. Returns the created version row.
create_slide_commentCreate a review comment anchored to a slide in a Lesson Studio project (Course Canvas comments). Stamped with the project current version; version 1 is auto-created if none exists.
list_slide_commentsList Course Canvas comments of a project (newest first), optionally filtered by version and/or resolved state. Enriched with composition title + slide order for reference.
update_slide_commentUpdate a Course Canvas comment body and/or its resolved state.
delete_slide_commentDelete a Course Canvas comment by id.
get_version_commentsGet all comments for a project version as a clipboard-ready text block, one comment per line prefixed with project/composition/slide references. Defaults to the project current version. Use for the “copy all comments → paste to Claude” flow.
expire_stale_studio_exportsFind Lesson Studio video exports stuck in a non-terminal state (pending/processing) older than N hours and mark them failed with an explanatory error_message, so an abandoned export stops looking like it is still running. Defaults to a DRY RUN — pass dry_run: false to actually write. RLS-scoped to the caller’s own projects.
start_composition_exportRender a whole composition into an MP4, server-side. Returns as soon as the job is accepted — rendering takes minutes, so this does NOT wait: poll get_studio_export with the returned export_id until it is completed, then pass that export to register_export_as_media to get a media_id the post calendar accepts. Recording a webcam over the deck is still done in the Lesson Studio UI; this exports what is there. Set variant to mobile for a 9:16 reel/short/story cut; omit it to follow the project’s own orientation.
get_studio_exportRead one export and, while it is still rendering, ask the render service where it is and write the answer back to the row. Poll this after start_composition_export until completed, then pass the export id to register_export_as_media. A failed export carries the real reason, not a generic one.
list_studio_exportsList the rendered video exports (MP4) of the caller’s Lesson Studio projects, newest first. Defaults to completed exports only. Use the returned export id with register_export_as_media to turn a video into a media_id the post calendar accepts.
register_export_as_mediaMake a completed Lesson Studio export (MP4) usable by the post calendar: creates a public.media row pointing at the exported file and returns the media_id plus the stable media.devfellowship.com/<id> link. Pass that media_id to draft_post_for_review on the campaigns MCP. Idempotent — the same export returns the same media_id. Only exports stored in the DevFellowship bucket can be registered.
split_export_for_storiesCut a completed export (MP4) into ordered Story parts of at most 60 s each, and register each part as media. The cuts follow the slide changes when the render service knows them, else scene changes. Returns the parts in order, each with a media_id — pass those media ids, in order, to the campaigns Story sequence. Waits up to ~45 s; if the split is still running, it returns split_job_id — then poll get_story_split with it.
check_studio_exportA machine review of a COMPLETED export (dfl-render /media/export-check): the duration (and the gap to expected_duration_s), the frame size, black-frame ranges, JPEG frames just after each slide starts, at its middle and just before it ends (slides from the render job), and a caption-over-face flag per mid-slide frame (one vision-LLM pass; face_check false skips it). flags lists what a reviewer must look at first. Waits ~50 s; if the check still runs it returns check_job_id — then call get_export_check.
get_export_checkRead an export check that check_studio_export started (the same answer when it is done).
get_story_splitRead a Story split job that split_export_for_stories started. While it runs, this returns its status — call it again. When it is done, it registers each part as media and returns the parts in order with their media ids (the same answer split_export_for_stories gives). Safe to call again: a part keeps its media_id.
list_templatesList all slide templates available in dfl-slide-templates (reads registry.json from GitHub).
search_templatesFind the best slide templates for an intent. Returns a RANKED shortlist (top-N) instead of the full list — give a natural-language intent (e.g. “compare two options head-to-head”, “mostrar uma captura de tela”, “one big hero number”) and get back the templates whose registry “when_to_use”/tags/media_profile best match, each with a relevance score and a one-line reason. Use this to PICK a template, then call get_template(id) for its slots. Deterministic + read-only (no auth needed).
get_templateFetch all source files (HTML landscape, HTML portrait, CSS landscape, CSS portrait, config YAML) for a specific template.
update_templateOpen a ready-to-merge PR in dfl-slide-templates with updated HTML/CSS and optional config YAML for a template. All provided fields are written in a single commit. Returns the PR URL. registry.json is edited as a MERGE: any registry_* field you omit keeps its current value, so a version bump can no longer erase the discoverability metadata search_templates ranks on. Pass null to delete a field. CREATING a template (an id not yet in registry.json) requires registry_version, registry_name, registry_when_to_use, registry_media_profile and registry_tags, so the new template is findable from the moment it merges.
list_themesList the CSS themes that actually exist in dfl-slide-templates at the pinned revision. The set is DERIVED from the repo (registry.json themes[]), so a theme added upstream appears here with no code change. Each entry reports whether its stylesheet resolved, plus its display name and light/dark mode when declared. The response names the commit it was read from and flags a degraded read.
get_themeFetch the CSS source for a specific theme.
update_themeOpen a ready-to-merge PR in dfl-slide-templates with updated CSS for a theme. Returns the PR URL. The PR also REGISTERS the theme in registry.json’s themes[] — the array themes_doc calls the source of truth and list_themes reads — so a theme published here is discoverable instead of being a stylesheet nothing lists. Registering a NEW theme requires theme_name and theme_mode: neither a brand’s display name nor its light/dark mode can be inferred, and a guess would become the source of truth. A new theme also needs its forbidden-colour contract in scripts/theme.config.json, or lint:css fails — that file is human-gated, so a brand-new theme cannot merge unattended.
render_slide_imageRender one stored slide to a PNG and return its URL. Wraps the deterministic headless capture in dfl-render with the CALLING user’s own credentials. Returns the dfl-slide-templates commit revision the template was read at AND a composition fingerprint of the deck state the derived slide index came from — together those two reproduce the render. canvas is optional; a canvas the template does not declare is REFUSED and no image is produced (never a fallback to the nearest canvas).
render_composition_imagesRender every surviving slide of a composition to a PNG and return the URLs in order_index order — the batch form of render_slide_image, and the tool a carousel needs. Soft-deleted slides are skipped. Captures run with bounded concurrency. Returns the dfl-slide-templates commit revision and ONE deck-level composition fingerprint shared by every image. canvas is optional; a canvas any template in the deck does not declare is REFUSED before anything is captured.
capture_cover_slideRender the composition’s cover slide (template_data.is_cover, set by set_cover_slide) to a PNG for the deck’s video thumbnail, and return its image_url. Errors clearly when the composition has no cover slide. This does NOT write projects.thumbnail_url — no tool in dfl-mcp-studio does yet — so persist the returned image_url yourself once such a tool exists.
generate_lesson_from_profileAuthor a NEW lesson topic in a specific tutor’s proven pedagogical style. Loads that tutor’s stored pedagogy profile (lms.tutor_profiles) and returns a Studio-importable GENERATION SPEC: a typology-biased slide plan (template picks ranked by the professor’s dominant lesson-type via search_templates’ ranker), the professor’s SIGNATURE as few-shot exemplars (verbatim metaphors/openers/cotidiano anchors), and amplify/reduce directives (amplify high-consistency moves, reduce vícios). Deterministic — the tool does NOT call an LLM; the calling agent expands each suggested slide into content, few-shotting the exemplars and honoring the directives. The rubric (HALF A) is never used for generation (de-circularization).

Create Lesson Studio Project

Create a new Lesson Studio project in the lesson_studio schema, plus a “Default” composition. Returns the project_id and the default composition_id (attach slides to a composition via create_slide).

ParameterTypeRequiredDescription
titlestringyesProject title
owner_idstringnoUUID of the owner (auth.users.id). Defaults to the authenticated caller.
orientationenumnoCanvas orientation (default: landscape). social-portrait = Feed/carousel post, 1080×1350 (4:5): static slides, no camera/captions/video export; only templates declaring the social-portrait canvas. One of: landscape, portrait, social-portrait.
descriptionstringnoOptional project description
settingsobjectnoOptional settings JSON, merged OVER the defaults (resolution, fps, backgroundColor, theme_id, and portrait’s 1:1 camera) rather than replacing them. Pass theme_id here to start on a theme other than the DFL one; update_studio_project changes it later.

List Lesson Studio Projects

List Lesson Studio projects owned by the caller (RLS-scoped), most recently updated first.

ParameterTypeRequiredDescription
limitnumbernoMax projects to return (default: 50, max: 100)
offsetnumbernoNumber of projects to skip (pagination)
searchstringnoSearch by title (ilike)

Delete Lesson Studio Project

Delete a Lesson Studio project. Its compositions, slides, slide_elements and slide_media cascade. RLS-scoped: only the caller’s own projects can be deleted.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project to delete

Update Lesson Studio Project

Update a project’s title, description, thumbnail, orientation, active theme (settings.theme_id), burned-in captions (settings.captions_*) or export watermark (settings.watermark). For a 9:16 reel, pass captions {enabled: true, position: “top”} and watermark {enabled: true} before start_composition_export. Camera defaults are set_project_camera’s job, not this tool’s. theme_id is checked against list_themes when the registry is reachable; an unknown id is rejected. A settings write (theme_id, captions, watermark) is guarded on updated_at (it reads settings first to merge; keys you leave out keep their stored value); a change to the other fields overwrites only the columns passed. Changing orientation does NOT reshape camera boxes — it warns and points at set_project_camera.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project
titlestringnoNew title
descriptionstringnoNew description, or null to clear it
thumbnail_urlstringnoNew thumbnail URL, or null to clear it
orientationenumnoCanvas orientation. social-portrait = Feed/carousel post, 1080×1350 (4:5): static slides, no camera/captions/video export; only templates declaring the social-portrait canvas. Changing an existing project to or from social-portrait is refused unless it has no slides (soft-deleted included); an empty project switching resets safe_area_top and camera settings to the target orientation defaults. One of: landscape, portrait, social-portrait.
theme_idstringnoTheme id to apply project-wide, as reported by list_themes (e.g. “default”, “devfellowship”).
captionsobjectnoBurned-in caption settings, merged key by key into settings.captions_*.
watermarkobjectnoExport watermark settings, merged key by key into settings.watermark. The logo comes from the theme.

Create Composition

Create a composition (lesson-level grouping / “frame”) under a Lesson Studio project. Slides attach to a composition.

ParameterTypeRequiredDescription
project_idstringyesUUID of the parent project
titlestringnoComposition title (default: “Untitled Composition”)
order_indexnumbernoDisplay order within the project. If omitted, appended after existing compositions.

List Compositions

List the compositions of a Lesson Studio project, ordered by order_index.

ParameterTypeRequiredDescription
project_idstringyesUUID of the parent project

Update Composition

Update a composition title and/or order_index.

ParameterTypeRequiredDescription
idstringyesUUID of the composition
titlestringnoNew title
order_indexnumbernoNew display order

Delete Composition

Delete a composition. Its slides must be removed or reassigned first (slides.composition_id is NOT NULL).

ParameterTypeRequiredDescription
idstringyesUUID of the composition to delete

Create Slide

Create a slide under a composition (mirrors the Lesson Studio editor insert shape so it is valid + editable in the UI). Optional template_data.animation animates the slide. Preset ‘steps-reveal’ shows list items one at a time; use it when the narration walks through steps, on a template whose items carry data-anim-target=“step”. Example: {preset:‘steps-reveal’,version:1,beat_source:‘manual’,beats:[{kind:‘reveal’,target:‘step’,index:0,start_ms:600,duration_ms:300}]}. Sort beats by start_ms. Every beat must end before the slide duration_ms. The response can list warnings. sfx: [{sound, at_ms, volume?}] — see list_sound_effects.

ParameterTypeRequiredDescription
composition_idstringyesUUID of the parent composition
project_idstringnoUUID of the parent project. Resolved from the composition if omitted.
order_indexnumbernoDisplay order within the composition. If omitted, appended at the end.
template_idstringnoSlide template id, as published in the dfl-slide-templates registry. Call search_templates(“<what the slide should do>”) or list_templates to pick one, and get_template(“<id>”) for its exact slots — this list is NOT enumerated here on purpose, so it can never go stale. An id that is not in the registry is rejected. Required slots must carry real content (see template_data). For a single image filling the whole canvas, prefer the dedicated create_image_slide tool.
template_dataobjectnoTemplate data JSON (text/props consumed by the template renderer). Validated against the slots the template declares in the registry (templates/<id>/config.yaml) — call get_template(“<id>”) to see each slot’s type and a valid sample. Required slots must be non-empty and correctly shaped: text fields need real text (not "" or whitespace); array fields need at least one entry matching the sample shape (e.g. bullet-list items: [{ “text”: “Agents run tools autonomously” }], table rows: [{ “cells”: [{ “value”: “Revenue” }] }]). Use template_id “blank” for an intentionally empty slide.
duration_msnumbernoSlide duration in ms, a positive integer (default: 5000)
background_colorstringnoHex background color (default: “#1a1a2e”). Only a color other than the default sets template_data.background_override = true, so the Studio shows it over the template background.
background_image_urlstringnoOptional background image URL. A non-empty URL also sets template_data.background_override = true.
transition_typestringnoTransition type (default: “fade”)
transition_duration_msnumbernoTransition duration in ms (default: 500)

Create Full-screen Image Slide

Create a slide that displays a single image filling the entire slide canvas, using the fullscreen-image template. Use this to bring your own finished slide images (e.g. an image hosted on S3, or a pre-rendered slide exported from another tool) instead of reusing the built-in content templates. The fit option controls how the image fills the canvas: cover (default) crops to fill, contain letterboxes to show the whole image.

ParameterTypeRequiredDescription
composition_idstringyesUUID of the parent composition
project_idstringnoUUID of the parent project. Resolved from the composition if omitted.
image_urlstringyesURL of the image to display full-screen (e.g. an S3 URL)
fitenumnoHow the image fills the canvas: “cover” (crop to fill, default) or “contain” (letterbox the whole image) One of: cover, contain.
bgstringnoLetterbox/background color (hex) used behind the image in “contain” mode. Defaults handled by the template CSS.
image_altstringnoAccessibility alt text for the image
order_indexnumbernoDisplay order within the composition. If omitted, appended at the end.
duration_msnumbernoSlide duration in ms (default: 5000)
background_colorstringnoHex background color (default: “#1a1a2e”)
transition_typestringnoTransition type (default: “fade”)
transition_duration_msnumbernoTransition duration in ms (default: 500)

List Slides

List the slides of a composition, ordered by order_index. Soft-deleted slides are excluded by default; pass include_deleted:true to return all (including soft-deleted).

ParameterTypeRequiredDescription
composition_idstringyesUUID of the parent composition
include_deletedbooleannoWhen true, also return soft-deleted slides (deleted_at IS NOT NULL). Default false → only live slides.

Update Slide

Update a slide. template_data replaces the slots; an omitted slot is removed. Studio keys (not slots): narration_notes, animation, background_override, camera, camera_box, capture, camera_box_custom, watermark, webcam_layout, pointer_track, is_cover, sfx, framing, caption_cues, clips, style_overrides, slide_name, caption_cues_source, caption_language, camera_segments, content_over_camera, caption_position, camera_focus, insert. An omitted Studio key is kept, except animation: a template_id change removes it unless you send it. Remove one via clear_template_data_keys, only when the user asks. narration_notes and pointer_track are user work and cannot be restored. The response lists removed_template_data_keys. A new background sets background_override = true. To show the template background again, send clear_template_data_keys: [‘background_override’] and no background field. A sent animation is checked as the create_slide description says, against the slide duration_ms. A new duration_ms is checked against the stored animation. The response can list warnings. sfx: [{sound, at_ms, volume?}] — see list_sound_effects.

ParameterTypeRequiredDescription
idstringyesUUID of the slide
order_indexnumbernoNew display order in the composition
composition_idstringnoMove the slide to another composition
template_idstringnoRegistry template id. An unknown id is rejected.
template_dataobjectnoTemplate slot values, validated against get_template(“<id>”).
clear_template_data_keysenum[]noStudio keys to remove; do not also send in template_data.
duration_msnumbernoSlide duration in ms (positive int)
background_colorstringnoHex background color
background_image_urlstringnoBackground image URL
transition_typestringnoTransition type
transition_duration_msnumbernoTransition duration, ms

Delete Slide

Delete a slide. By default this is a SOFT delete (recoverable via restore_slide; slide_elements/slide_media are kept). Pass hard:true for a permanent delete (slide_elements + slide_media cascade).

ParameterTypeRequiredDescription
idstringyesUUID of the slide to delete
hardbooleannoWhen true, permanently delete the slide (slide_elements + slide_media cascade). Default false → soft delete (recoverable via restore_slide).

Restore Slide

Restore a soft-deleted slide (clears deleted_at/deleted_by so it reappears in list_slides). Only works on slides that were soft-deleted via delete_slide; a hard-deleted slide is gone permanently.

ParameterTypeRequiredDescription
idstringyesUUID of the slide to restore

List Animation Presets

The slide animation presets set_slide_animation can apply, with the template_data slots each one reads. Pass template_id to also learn which of them that template can actually run: the answer is read from the live template markup (data-anim-target), so it cannot go stale. A preset whose slots the slide does not carry produces no beats — that is what requires describes.

ParameterTypeRequiredDescription
template_idstringnoSlide template id, as published in the dfl-slide-templates registry. When given, each preset also reports runs_on_template and the targets the template declares.

Set Slide Animation

Animate a slide with one of the presets from list_animation_presets. With no beats, the timings are computed from the slide’s own template_data and duration_ms — the same defaults the Lesson Studio editor applies, already fitted to the slide length and valid at 24, 30 and 60 fps. Pass beats only for a timing the defaults do not express. “zoom-focus” is the one preset with no template slot — it works on any slide, pushing in and holding until a later zoom beat frames back out. A “zoom” beat also COMPOSES with every other preset (add one to the “beats” array of a steps-reveal/chat-typing/highlight/image-enter spec): it drives the synthetic “slide” target no template markup ever declares, so it never collides with that preset’s own beats. Writes template_data.animation and leaves every other template_data key untouched. To remove an animation, call update_slide with clear_template_data_keys: [‘animation’].

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide to animate
presetenumyesAnimation preset. A preset the slide’s template cannot drive is REFUSED here, not silently stored: call list_animation_presets(“<template_id>”) to see which ones run. A preset whose targets the template does not declare drives nothing, and one whose slots the slide does not carry is rejected here. “zoom-focus” always runs_on_template: true — it reads no slot and needs no declared target. “element-reveal” is the same kind of preset for the free-canvas elements of the slide (slide_elements rows, never a template_data slot): it needs at least one element on the slide. One of: steps-reveal, chat-typing, highlight, image-enter, zoom-focus, element-reveal.
beatsobject[]noExplicit beats, sorted by start_ms, every one ending before the slide duration_ms. Omit this to get the preset defaults fitted to the slide — that is the recommended path. Sending beats marks the spec beat_source “manual”, so the editor will not silently re-time them. A “zoom” beat ({ kind: “zoom”, target: “slide”, start_ms, duration_ms?, rect: { x, y, width } }) may be mixed into ANY preset’s beats to push in on part of the canvas; rect is fractions of the canvas (same shape as camera_box), width floored at 0.1 (10x zoom); a rect of { x:0, y:0, width:1 } zooms back out to the full canvas. A “reveal” beat on target “element” (one of a slide’s free-canvas boxes, see create_slide_element/list_slide_elements for the index) may also carry: effect, one of “fade”, “rise”, “drop”, “slide-left”, “slide-right”, “zoom-in”, “shrink-in”, “spin-shrink”, “pop”, “blur-in”, “swing-in”, “bounce-in”, “scramble-in”, “glitch-in”, “slide-up”, “slide-down”, “flip-in”, “wipe-right”, “wipe-up”, “typewriter-in”, “count-up”, “pulse”, “shake”, “wobble”, “tada”, “heartbeat”, “spin”, “ken-burns”, “float”, “breathe”, “fade-out”, “drop-out”, “shrink-out”, “spin-out”, “blur-out”, “glitch-out”, “rise-out”, “slide-left-out”, “slide-right-out”, “zoom-through-out”, “wipe-left-out” — plays instead of the default fade + rise; and focus (boolean) — dims and blurs the rest of the slide while this box is the subject, for a camera-lens feel with no separate zoom beat. Example: { kind: “reveal”, target: “element”, index: 0, start_ms: 600, duration_ms: 400, effect: “pop”, focus: true }.
tracksobject[]noKeyframe tracks for free-canvas boxes (spec version 2). Each track is { index, property, keyframes: [{ t_ms, value, easing }] }: property is one of “x”, “y”, “scale”, “rotate”, “opacity”, “blur”; easing one of “linear”, “in”, “out”, “inOut”, “hold” and shapes the segment that STARTS at that keyframe (“hold” jumps at the next one). Values are relative to the box rest pose: x/y are design-pixel offsets, rotate a degree offset, blur added px, scale and opacity (0..1) multipliers. Keyframes strictly increasing in t_ms and before the slide end; one track per box and property; a box revealed by a beat with no effect cannot take tracks. With tracks and no beats the spec animates the boxes by tracks alone (no default entrances are added). Example: { index: 0, property: “x”, keyframes: [{ t_ms: 0, value: 0, easing: “inOut” }, { t_ms: 1200, value: 300, easing: “linear” }] }.
formulasobject[]noFormula-driven box properties (spec version 2): { index, property, source, start_ms, end_ms }. source is an expression (max 200 chars) over t (seconds), p (0..1 across [start_ms, end_ms]), i (box index), w/h (the canvas) with + - * / % ^, unary minus, pi and sin cos abs min max clamp lerp step smoothstep noise(x, seed?). Same units as a track value; outside its window the value holds its end values; a non-finite result is identity. One driver per box and property: a property with a track cannot also take a formula. A parse error names the character position. Example: { index: 0, property: “y”, source: “sin(t * 2 * pi) * 20”, start_ms: 0, end_ms: 3000 }.
canvasobjectnoDesign canvas a formula reads as w/h. Defaults to the project’s design canvas: 1280x720 landscape, 720x1280 portrait.
behavioursobject[]noOptional metadata naming the behaviour that wrote a box’s formulas, so the Lesson Studio picker re-opens it: { index, id, params: { name: number }, owned: [properties] }, one per box; every owned property needs a formula on that box. It is NOT expanded here: send the formulas it stands for (the Studio catalog: float → y, orbit → x/y, shake → x/y, pulse → scale, drift → x/y, typewriter-cursor → opacity).

Search Slide Images

Search the stock photo library for an image to put on a slide, and get back candidate URLs. Use it to fill an image-type template slot without a human opening the editor: pick a result and write its url with update_slide (template_data.image) or create_image_slide (image_url). This tool only reads — it never touches a slide. Match orientation to the project canvas so the crop is not fighting the layout. Results carry photographer and pexels_url: the licence asks for that credit, so keep it with the image.

ParameterTypeRequiredDescription
querystringyesWhat to look for, in English — e.g. “developer at a laptop”, “solar panels”. Stock search is keyword-based, so a short concrete noun phrase beats a sentence.
orientationenumnoCrop to ask the provider for. Defaults to landscape; pass portrait for a 9:16 project. social-portrait (Feed, 1080×1350 4:5) searches as portrait. One of: landscape, portrait, social-portrait.
per_pagenumbernoHow many candidates to return (default 8, max 20).

Set Cover Slide

Mark a slide as the deck’s cover (template_data.is_cover), or unmark it (is_cover: false). The cover is used for the video thumbnail via capture_cover_slide, but dfl-render EXCLUDES it from the rendered video, its slide count, and the 01/09 deck index. Setting is_cover: true clears the flag from every other slide in the same composition first — a deck has at most one cover. Preserves every other template_data key. To set the composition’s thumbnail after capturing it, chain the result into whichever tool writes projects.thumbnail_url (this tool never writes it).

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide to mark or unmark as the cover
is_coverbooleannotrue (the default) to make this slide the cover; false to unmark it

List Slide Beats

List the beats of a slide’s template_data.animation, each tagged with the beat_index add_slide_beat/update_slide_beat/remove_slide_beat address. Returns an empty beats array (not an error) when the slide carries no animation.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide

Add Slide Beat

Insert one beat into a slide’s template_data.animation.beats. The slide must already carry an animation (create one with set_slide_animation first). Beats must stay sorted by start_ms; a beat or at_index that breaks the order is rejected with the same validator set_slide_animation uses. Marks beat_source “manual”. Returns the new beat_index.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide
beatobjectyesThe beat to insert — same shape set_slide_animation’s “beats” entries take, e.g. {kind:“zoom”, target:“slide”, start_ms, rect:{x,y,width}} or {kind:“reveal”, target:“step”, index, start_ms, duration_ms}.
at_indexnumbernoPosition to insert at (0 = first). Omit to append at the end.

Update Slide Beat

Replace one beat of a slide’s template_data.animation.beats, addressed by beat_index (from list_slide_beats). beat replaces the ENTIRE beat, not just the fields you pass — send every field the beat needs, same shape set_slide_animation’s “beats” entries take. Beats must stay sorted by start_ms after the replacement. Marks beat_source “manual”.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide
beat_indexnumberyesIndex of the beat to replace (from list_slide_beats)
beatobjectyesThe full replacement beat, including kind

Remove Slide Beat

Remove one beat from a slide’s template_data.animation.beats, addressed by beat_index (from list_slide_beats). A spec needs at least one beat, so removing the only beat is rejected — use update_slide with clear_template_data_keys: [‘animation’] to drop the animation entirely. Marks beat_source “manual”.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide
beat_indexnumberyesIndex of the beat to remove (from list_slide_beats)

Create Slide Element

Create a free-canvas box on a slide: a text box, an image, or a shape drawn on top of the slide’s template. position_x/position_y/width/height/rotation are DESIGN pixels — the canvas is 1280x720 for a landscape project and 720x1280 for a portrait one (the parent project’s own orientation, not an argument here). A box entirely outside that canvas is rejected; a box that only partly overlaps it is allowed (that is a normal partial-off-canvas placement, not an error). content is validated by type: text.text is at most 2000 characters; image.url must be an http(s) URL; every color (text.color, shape.fill, shape.stroke, …) must be a hex color (#rgb, #rrggbb, #rrggbbaa) or an rgba()/rgb() function; an unknown content key is rejected. A ‘text’ box may omit content fields — missing ones are filled from the Studio’s own default text style, so { type: ‘text’, content: { text: ‘Hello’ } } is enough for a fully-styled box. ‘image’ and ‘shape’ boxes must supply every field their type needs. The response carries animation_index: the box’s position among the slide’s elements, ordered by creation time — THIS is the index a set_slide_animation “element-reveal” beat ({ kind: “reveal”, target: “element”, index }) must use to animate THIS box, because that index is the box’s position in the stored array, never its z_index or the order it was drawn on screen. Example: create_slide_element({ slide_id, type: “text”, content: { text: “Welcome” }, position_x: 100, position_y: 80, width: 600, height: 120 }) → { element: {…}, animation_index: 0 }.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide to add the box to
typeenumyesBox kind: text, image, or shape One of: text, image, shape.
contentobjectnoType-specific content. text: { text, fontFamily?, fontSize?, fontWeight?, color?, textAlign?, lineHeight?, letterSpacing?, fontStyle?, textTransform?, backgroundColor?, verticalAlign? } — missing fields fall back to the Studio default text style. image: { url, alt, objectFit, crop?, enter?, growFrom? } — url, alt and objectFit are required, no defaults. shape: { shapeType, fill, stroke, strokeWidth, borderRadius? } — all of shapeType/fill/stroke/strokeWidth are required.
position_xnumberyesLeft edge, design pixels
position_ynumberyesTop edge, design pixels
widthnumberyesBox width, design pixels
heightnumberyesBox height, design pixels
rotationnumbernoDegrees, clockwise (default 0)
opacitynumberno0 (invisible) to 1 (opaque); default 1
z_indexnumbernoStacking order among the slide’s boxes. Default: one above the highest existing box.

List Slide Elements

List a slide’s free-canvas boxes (text/image/shape), ordered by creation time. Each element carries animation_index: its position in this order, and the exact index a reveal beat with target: "element" must reference to animate it — never the box’s z_index or draw order. Example response: { elements: [{ id, type: “text”, content: {…}, position_x, position_y, width, height, rotation, opacity, z_index, animation_index: 0 }, …] }.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide

Update Slide Element

Update a free-canvas box’s content and/or geometry (position_x/position_y/width/height/rotation/opacity/z_index). Every field is optional — send only what changes. content is a PARTIAL patch: it is merged over the box’s stored content and the merged object is re-validated against its type’s rules (same rules as create_slide_element — text max 2000 chars, image url must be http(s), colors must be hex or rgba()/rgb(), unknown keys rejected). Geometry is design pixels, same canvas rule as create_slide_element: a box moved/resized fully outside the canvas is rejected. The box’s type and its animation_index (set by create order) cannot be changed here. Example: update_slide_element({ id, content: { text: “Updated headline” }, position_x: 140 }).

ParameterTypeRequiredDescription
idstringyesUUID of the slide element
contentobjectnoPartial content patch, merged then re-validated
position_xnumbernoLeft edge, design pixels
position_ynumbernoTop edge, design pixels
widthnumbernoBox width, design pixels
heightnumbernoBox height, design pixels
rotationnumbernoDegrees, clockwise
opacitynumberno0 (invisible) to 1 (opaque)
z_indexnumbernoStacking order among the slide’s boxes

Delete Slide Element

Delete a free-canvas box and repair the slide’s animation in the same call. A reveal beat (target: "element") addresses a box by its position among the slide’s elements (animation_index), so removing a box shifts every later box’s beat index down by one — this tool does that shift automatically: the removed box’s own beat is dropped, later beats are reindexed keeping their effect/focus, and if no element beat survives template_data.animation is removed entirely (an animation with zero beats is not valid). The response reports removed_beat_count, shifted_beat_count and animation_cleared so the caller knows exactly what changed.

ParameterTypeRequiredDescription
idstringyesUUID of the slide element to delete

Get Camera

Read the camera box + visibility for a project (project_id) and optionally one of its slides (slide_id). Reports the RAW override at each level (project default, slide override) plus the RESOLVED value that actually applies, following the same precedence dfl-render uses (slide, then project, then the built-in default). Pass slide_id alone to also resolve the project it belongs to.

ParameterTypeRequiredDescription
project_idstringnoUUID of the project. Required unless slide_id is given.
slide_idstringnoUUID of a slide, to also report its resolved/override camera.

Set Project Camera

Set the project-wide camera default: aspect (16:9 or 1:1), box (preset size+corner or custom fractions), and default visibility (off/overlay). Applies to every slide that does not override its own camera via set_slide_camera. Changing aspect with no box in the same call reshapes the current project box (and every slide’s own camera_box override) to fit the new aspect, mirroring the Lesson Studio editor. Guarded on updated_at. focus {x, y} (0..1 of the camera frame) sets the project default crop point (settings.camera_focus); a slide focus (set_slide_camera) wins.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project
aspectenumnoCamera shape. Omit to leave it unchanged. One of: 16:9, 1:1.
boxobject | objectnoNew project-default camera box.
clear_boxbooleannoRemove settings.camera_box, so the project falls back to the built-in default.
default_visibilityenumnoCamera visibility for a slide with no override (default: overlay). One of: off, overlay.
clear_default_visibilitybooleannoRemove settings.camera_default.
focusobjectnoProject default camera crop point (settings.camera_focus).
clear_focusbooleannoRemove settings.camera_focus (the centre).

Set Slide Camera

Override this slide’s camera box and/or visibility, on top of the project default set_project_camera sets. box resolves against the parent project’s orientation and camera aspect. clear_box removes the box override (the slide follows the project box again); clear_visibility removes the visibility override. segments limits WHEN the corner (picture-in-picture) camera is drawn — refused on a fullscreen-webcam slide: a list of { start_ms, end_ms } windows in slide time (whole ms, sorted, non-overlapping, within the slide duration, 1-50 of them); the voice keeps playing outside them. clear_segments shows the camera for the whole slide again. To hide it on the whole slide use visibility: ‘off’. focus {x, y} (0..1 of the camera frame) moves the crop of the camera off the centre, e.g. {x: 0.3, y: 0.5} for a face left of centre in a 16:9 take on a portrait slide; clear_focus goes back to the project focus / the centre. Preserves every other template_data key. Guarded on updated_at.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide
boxobject | objectnoNew camera box override for this slide.
clear_boxbooleannoRemove this slide’s camera_box override.
visibilityenumnoCamera visibility override for this slide. One of: off, overlay.
clear_visibilitybooleannoRemove this slide’s camera visibility override.
segmentsobject[]noWindows (slide ms) in which the camera is drawn; stored as template_data.camera_segments.
clear_segmentsbooleannoRemove this slide’s camera_segments (camera on the whole slide).
focusobjectnoThe point of the camera frame the crop keeps (template_data.camera_focus).
clear_focusbooleannoRemove this slide’s camera_focus (the project focus, else the centre).

List Sound Effects

The sound-effect catalog available to template_data.sfx / set_slide_sfx: every valid sound key, its category, use-case hint and duration. Read-only, no database access. Pass category to filter.

ParameterTypeRequiredDescription
categorystringnoRestrict to one category id (see the returned categories list).

Set Slide Sound Effects

Write this slide’s sound effects (template_data.sfx). Each entry is {sound, at_ms, volume?} — sound is a catalog key from list_sound_effects, at_ms must start before the slide duration_ms. mode “replace” (default) discards the stored list; “append” adds to it. An empty array with “replace” removes the key. Preserves every other template_data key. Guarded on updated_at.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide
sfxobject[]yesThe sound effects to write (or add, in append mode).
modeenumnoDefault “replace”. One of: replace, append.

Set Slide Captions

Replace a slide’s caption cues (template_data.caption_cues). Cues use the recording’s SOURCE clock in ms (the file, not the slide), sorted and non-overlapping, max 2000. source_stamp ‘current’ (default) stamps caption_cues_source with the slide’s recording URL, so the Studio editor does not re-transcribe and overwrite them; ‘none’ removes the stamp (the editor re-transcribes). Keeps every other template_data key. Guarded on updated_at.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide
cuesobject[]yesCues on the recording (SOURCE) clock, ms: sorted, non-overlapping, endMs > startMs, text non-empty.
languagestringnoLanguage of the cues, BCP-47 (pt, en, pt-BR). Stored as caption_language.
source_stampenumno’current’ (default): stamp with the slide recording URL. ‘none’: remove the stamp. One of: current, none.

Edit Caption Cue

Edit ONE cue of a slide’s caption_cues by its 0-based index: new text and/or startMs/endMs (source clock, ms). The whole list must stay sorted and non-overlapping. Keeps the caption_cues_source stamp and every other key. Guarded on updated_at.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide
indexnumberyes0-based index of the cue in caption_cues
textstringnoNew text (non-empty)
startMsnumbernoNew start, source ms
endMsnumbernoNew end, source ms

Set Slide Layout

Apply a camera/content layout preset to one slide. ‘fullscreen-camera’: the webcam fills the frame, no slide content. ‘overlay-top’: webcam fullscreen, slide content drawn OVER it in the top half (content_over_camera). ‘overlay-full’: webcam fullscreen, slide content drawn OVER it on the WHOLE canvas (no safe band, scale 1) — for a full-canvas image. ‘split-top-image’: slide content on top, full-width camera band at the bottom (portrait only). The band crop keeps the FACE: it finds the face in the slide’s recording and writes camera_focus there (else it keeps the upper part of the frame); an existing camera_focus is kept. On an image-only slide (fullscreen-image, no free elements) it trims the image’s flat margins and contains the drawn area in the top band (content_zoom/offset; the trim is recorded in image_trim); a slide with text keeps the plain band fit. ‘background-pip’: slide content fullscreen, small camera bottom-right. Resolves boxes against the project orientation and camera aspect. One write, guarded on updated_at; returns the written and removed keys. Keeps every other template_data key. caption_position (with or without a preset) sets this slide’s caption placement, e.g. ‘over-camera’ on a split slide so the captions do not cover the image.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide
presetenumnoThe layout preset. Optional when caption_position is set. One of: fullscreen-camera, overlay-top, overlay-full, split-top-image, background-pip.
caption_positionenumnoWhere THIS slide’s burned-in captions sit, over the project setting: top | middle | bottom (project semantics, the camera box is avoided), over-camera (bottom edge, NOT avoiding the camera: on a split slide the captions sit on the camera band, off the image), project (remove the override). One of: top, middle, bottom, over-camera, project.

Transcribe Media

Transcribe a video/audio (URL or media_id) with dfl-render, optionally translated. Returns the text and caption cues {text, startMs, endMs} on the source clock. Waits up to ~45 s; if still running it returns job_id — then call get_media_transcription. Writes nothing; attach_slide_recording transcribes on its own.

ParameterTypeRequiredDescription
sourceobject | objectyesThe source: {url} or {media_id}.
languageenumnoSpoken language; default ‘auto’. One of: auto, pt, en.
translate_tostringnoAlso return the cues translated to this language.
project_idstringnoUse this project’s caption glossary (set_project_glossary).
glossarystring[]noCanonical terms (names, tickers) the captions must spell right. Default: the project glossary.
fix_cuesbooleannoAlso run the LLM cue-fix pass (word order, misheard words, glossary). Default false.

Get Media Transcription

Read a transcription job that transcribe_media started: its status while it runs, else the text, cues and translation.

ParameterTypeRequiredDescription
job_idstringyesThe job_id transcribe_media returned.

Attach Slide Recording

Attach a raw recording (URL or media_id) to a slide: writes its slide_media row (one per slide), sets the slide duration to the trim window, starts the server transcode, and writes captions — transcribed by dfl-render (optionally translated) or the cues you pass — stamped so the editor keeps them. Waits up to ~45 s; if the transcription still runs, it returns transcription_job_id: then call get_slide_recording. WARNING: an open Lesson Studio tab on this project can overwrite the row on autosave; close it first.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide
sourceobject | objectyesThe source: {url} or {media_id}.
media_typeenumnoDefault ‘webcam_video’. One of: webcam_video, screen_recording, audio.
webcam_layoutenumnoAlso set the slide webcam_layout. One of: fullscreen, pip.
trimobjectnoWindow of the recording that plays on the slide, SOURCE ms [start, end).
duration_msnumbernoSource length, ms. Else estimated from the transcription.
transcribebooleannoDefault true. Ignored when captions is given.
languageenumnoSpoken language; default ‘auto’. One of: auto, pt, en.
translate_tostringnoWrite the captions translated to this language.
captionsobject[]noWrite these cues (source clock) and skip transcription.
glossarystring[]noCanonical terms (names, tickers) the captions must spell right. Default: the project glossary.
fix_cuesbooleannoAlso run the LLM cue-fix pass (word order, misheard words, glossary). Default false.

Get Slide Recording

Read a slide’s recording: source URL, trim window, transcode_status (export needs ‘done’; ‘processing’ still runs) and captions. With transcription_job_id (from attach_slide_recording) it finishes the attach once the job is done: fills an unknown length and writes the stamped captions, unless the slide already has captions for this recording.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide
transcription_job_idstringnoThe job attach_slide_recording returned.

Split Recording Across Slides

Spread ONE raw take over several slides. mode window (default): every slide points at the same file with its own trim window [start_ms, end_ms), keep ranges become template_data.clips, and ONE transcription of the whole file gives each slide ONLY its own cues (a cue goes to the slide whose window, or keep range, holds its midpoint, clamped to it). mode cut: dfl-render cuts real files (/media/cut, optional fit) and each slide gets its part, with its own cues shifted onto it. create_slides appends new slides for parts without slide_id. Waits ~45 s; if a job still runs it returns job ids to resume with. An open Studio tab on the project can overwrite the rows on autosave.

ParameterTypeRequiredDescription
sourceobject | objectyesThe source: {url} or {media_id}.
partsobject[]yesOne entry per slide, in slide order.
create_slidesobjectno—
modeenumnoDefault ‘window’. One of: window, cut.
fitenumnocut mode only. One of: none, portrait-crop, portrait-letterbox.
media_typeenumnoDefault ‘webcam_video’. One of: webcam_video, screen_recording, audio.
duration_msnumbernoSource length, ms, when known.
transcribebooleannoDefault true.
languageenumnoOne of: auto, pt, en.
translate_tostringnoLanguage of the cues, BCP-47 (pt, en, pt-BR). Stored as caption_language.
transcription_job_idstringnoResume: the job a previous call returned.
cut_job_idstringnoResume: the cut job a previous call returned.
glossarystring[]noCanonical terms (names, tickers) the captions must spell right. Default: the project glossary.
fix_cuesbooleannoAlso run the LLM cue-fix pass (word order, misheard words, glossary). Default false.

Import Media From URL

Import a clip from a public video URL (e.g. a YouTube/X post) through dfl-render: download, optional [start_s, end_s) cut, optional portrait fit, transcription and translation. register_media makes it a public.media row; attach_to_slide_id attaches it to a slide with its (translated) cues, with no second transcription. as_insert (with attach_to_slide_id) makes it an INSERT slide: the clip on its own slide, contained, no camera treatment, optional insert_label. Waits ~45 s; else returns job_id — then call get_media_import. Use only media you may reuse.

ParameterTypeRequiredDescription
urlstringyesPublic page or file URL of the video.
start_snumbernoClip start, seconds.
end_snumbernoClip end, seconds.
fitenumnoOne of: none, portrait-crop, portrait-letterbox.
transcribebooleannoDefault true.
languageenumnoOne of: auto, pt, en.
translate_tostringnoTranslate the cues to this language.
attach_to_slide_idstringnoAttach the clip to this slide.
webcam_layoutenumnoWith attach_to_slide_id. One of: fullscreen, pip.
as_insertbooleannoWith attach_to_slide_id: make it an INSERT slide (its own slide, contained, no camera treatment).
insert_labelstringnoWith as_insert: a name label under the clip.
register_mediabooleannoAlso create a public.media row (default false).

Get Media Import

Read an import_media_from_url job. While it runs, its status. When done, it does the same finish: register_media and/or attach_to_slide_id (pass them again). Safe to call again.

ParameterTypeRequiredDescription
job_idstringyesThe job_id import_media_from_url returned.
attach_to_slide_idstringno—
webcam_layoutenumnoOne of: fullscreen, pip.
as_insertbooleanno—
insert_labelstringno—
register_mediabooleanno—

Set Slide Insert

Make a slide an INSERT slide: an inserted video on its own slide (the video cuts from the camera to the clip, then back). The slide recording plays as a screen recording — contained, no camera box, no camera focus, no face crop — and the camera keys go. label (e.g. “@rohanpaul_ai”) adds a name label box under the clip, drawn above the video in the export; label null removes it. The slide needs a recording (import_media_from_url with as_insert does all of this in one call). Captions stay as they are.

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide with the clip.
labelstringnoName label text; null removes it; omit to keep it.

Create Project From Video

Turn one raw take into a new Lesson Studio project in one call: creates the project (portrait by default, no project camera box) with burned-in caption settings, a fullscreen-camera slide per part (default: one slide, the whole take), attaches the recording, transcribes (optionally translates) and writes stamped captions. parts has the split_recording_across_slides shape. Returns project, composition and slide ids; if a job still runs it returns the ids to finish with get_slide_recording or split_recording_across_slides.

ParameterTypeRequiredDescription
titlestringyesProject title
sourceobject | objectyesThe source: {url} or {media_id}.
orientationenumnoDefault ‘portrait’. One of: portrait, landscape.
captionsobjectno—
partsobject[]noDefault: one slide, the whole take.
modeenumnoWith parts. Default ‘window’. One of: window, cut.
webcam_layoutenumnoDefault ‘fullscreen’. One of: fullscreen, pip.
duration_msnumbernoSource length, ms, when known.
languageenumnoOne of: auto, pt, en.
translate_tostringnoLanguage of the cues, BCP-47 (pt, en, pt-BR). Stored as caption_language.
glossarystring[]noCaption glossary: stored on the new project and applied now.
fix_cuesbooleannoAlso run the LLM cue-fix pass (word order, misheard words, glossary). Default false.

Set Project Caption Glossary

Set a Lesson Studio project’s caption glossary: the canonical spellings (names, tickers — ‘Bessent’, ‘USDC’) that the server transcription applies to the captions (a whole-word near match becomes the term; timings never change). transcribe_media (with project_id), attach_slide_recording, split_recording_across_slides and create_project_from_video use it by default. Replaces the list; terms: [] clears it. Stored in projects.settings.caption_glossary; guarded on updated_at.

ParameterTypeRequiredDescription
project_idstringyesThe project.
termsstring[]yesThe full list (replaces the old one).

Upload Slide Image

Upload an image (base64 PNG, JPEG, WebP or GIF, at most 6 MB; the type is read from the bytes, SVG is refused) to DFL media as the caller, PUBLIC (the editor and the export load slide images by URL). Returns the url. With slide_id it also places the image as an image box on that slide: by default the WHOLE canvas (720x1280 portrait, 1280x720 landscape) with fit ‘contain’, above the other boxes — pair it with set_slide_layout ‘overlay-full’ for a full-canvas overlay over a fullscreen camera. box {x,y,width,height} in design pixels places it elsewhere. Use it in place of an upload outside the Studio.

ParameterTypeRequiredDescription
file_contentstringyesThe image, base64.
file_namestringyesA name for the file; the extension comes from the bytes.
slide_idstringnoPlace the image on this slide.
altstringnoAlt text for the box (default: the file name).
fitenumnoDefault ‘contain’. One of: contain, cover, fill.
boxobjectnoDesign pixels. Default: the whole canvas.

List Project Versions

List the versions of a Lesson Studio project, ordered by version_number descending (current/highest first).

ParameterTypeRequiredDescription
project_idstringyesUUID of the project

Bump Project Version

Create a new version of a Lesson Studio project (version_number = max existing + 1, starting at 1). Subsequent comments are stamped to this new version. Returns the created version row.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project to bump
labelstringnoOptional human label for this version (e.g. “after canvas review pass 1”)

Create Slide Comment

Create a review comment anchored to a slide in a Lesson Studio project (Course Canvas comments). Stamped with the project current version; version 1 is auto-created if none exists.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project
bodystringyesThe comment text (change request / note)
slide_idstringnoUUID of the slide the comment targets
composition_idstringnoUUID of the composition (for reference / canvas grouping)
anchor_xnumbernoOptional Figma-style pin X coordinate on the canvas
anchor_ynumbernoOptional Figma-style pin Y coordinate on the canvas

List Slide Comments

List Course Canvas comments of a project (newest first), optionally filtered by version and/or resolved state. Enriched with composition title + slide order for reference.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project
project_version_idstringnoFilter to comments stamped with this version
resolvedbooleannoFilter by resolved state (true/false)

Update Slide Comment

Update a Course Canvas comment body and/or its resolved state.

ParameterTypeRequiredDescription
idstringyesUUID of the comment
bodystringnoNew comment text
resolvedbooleannoMark resolved (true) or reopen (false)

Delete Slide Comment

Delete a Course Canvas comment by id.

ParameterTypeRequiredDescription
idstringyesUUID of the comment to delete

Get Version Comments (clipboard text)

Get all comments for a project version as a clipboard-ready text block, one comment per line prefixed with project/composition/slide references. Defaults to the project current version. Use for the “copy all comments → paste to Claude” flow.

ParameterTypeRequiredDescription
project_idstringyesUUID of the project
project_version_idstringnoUUID of the version. If omitted, the current (max version_number) is used.

Expire Stale Lesson Studio Exports

Find Lesson Studio video exports stuck in a non-terminal state (pending/processing) older than N hours and mark them failed with an explanatory error_message, so an abandoned export stops looking like it is still running. Defaults to a DRY RUN — pass dry_run: false to actually write. RLS-scoped to the caller’s own projects.

ParameterTypeRequiredDescription
older_than_hoursnumbernoOnly expire exports whose started_at is at least this many hours ago (default 6, minimum 1). Keep this comfortably above your longest legitimate render.
project_idstringnoRestrict the sweep to a single project UUID. Omit to sweep all of the caller’s projects.
statusesstring[]noWhich non-terminal statuses to sweep (default [“pending”,“processing”]).
reasonstringnoMessage written to error_message. Defaults to a user-facing explanation.
limitnumbernoMaximum number of rows to expire in one call (safety cap).
dry_runbooleannoWhen true (the DEFAULT), only report what WOULD be expired without writing.

Export a Lesson Studio composition to MP4

Render a whole composition into an MP4, server-side. Returns as soon as the job is accepted — rendering takes minutes, so this does NOT wait: poll get_studio_export with the returned export_id until it is completed, then pass that export to register_export_as_media to get a media_id the post calendar accepts. Recording a webcam over the deck is still done in the Lesson Studio UI; this exports what is there. Set variant to mobile for a 9:16 reel/short/story cut; omit it to follow the project’s own orientation.

ParameterTypeRequiredDescription
composition_idstringyesThe composition to render (from list_compositions).
variantenumnoAspect to cut to: desktop is landscape 16:9 (YouTube), mobile is portrait 9:16 (Instagram reels, YouTube shorts, stories). Omit to follow the project orientation (set at create_studio_project, changeable with update_studio_project). Use mobile to cut a portrait video out of a landscape project without changing it. One of: desktop, mobile.
resolutionenumnoOutput size. Omit for the render service default (1080p). One of: 720p, 1080p, 4k.
fpsnumbernoOutput frame rate. Omit for the render service default.

Get one Lesson Studio export

Read one export and, while it is still rendering, ask the render service where it is and write the answer back to the row. Poll this after start_composition_export until completed, then pass the export id to register_export_as_media. A failed export carries the real reason, not a generic one.

ParameterTypeRequiredDescription
export_idstringyesThe export to read (from start_composition_export).

List Lesson Studio Exports

List the rendered video exports (MP4) of the caller’s Lesson Studio projects, newest first. Defaults to completed exports only. Use the returned export id with register_export_as_media to turn a video into a media_id the post calendar accepts.

ParameterTypeRequiredDescription
project_idstringnoRestrict to one project.
statusenumnoExport status to match (default completed). all lists every state. One of: completed, failed, pending, processing, all.
limitnumbernoMaximum rows (default 20, max 100).

Register a Lesson Studio Export as Post Media

Make a completed Lesson Studio export (MP4) usable by the post calendar: creates a public.media row pointing at the exported file and returns the media_id plus the stable media.devfellowship.com/&lt;id> link. Pass that media_id to draft_post_for_review on the campaigns MCP. Idempotent — the same export returns the same media_id. Only exports stored in the DevFellowship bucket can be registered.

ParameterTypeRequiredDescription
export_idstringyesAn export id from list_studio_exports (status must be completed).
namestringnoDisplay name for the media row. Defaults to the export file name.

Split a Lesson Studio export into Instagram Story parts

Cut a completed export (MP4) into ordered Story parts of at most 60 s each, and register each part as media. The cuts follow the slide changes when the render service knows them, else scene changes. Returns the parts in order, each with a media_id — pass those media ids, in order, to the campaigns Story sequence. Waits up to ~45 s; if the split is still running, it returns split_job_id — then poll get_story_split with it.

ParameterTypeRequiredDescription
export_idstringyesA completed export (from get_studio_export or list_studio_exports).
max_segment_snumbernoLongest part in seconds (6–60). Omit for the render service default (59).

Check a Lesson Studio export

A machine review of a COMPLETED export (dfl-render /media/export-check): the duration (and the gap to expected_duration_s), the frame size, black-frame ranges, JPEG frames just after each slide starts, at its middle and just before it ends (slides from the render job), and a caption-over-face flag per mid-slide frame (one vision-LLM pass; face_check false skips it). flags lists what a reviewer must look at first. Waits ~50 s; if the check still runs it returns check_job_id — then call get_export_check.

ParameterTypeRequiredDescription
export_idstringyesA completed export (get_studio_export / list_studio_exports).
expected_duration_snumbernoThe length you expect, seconds.
face_checkbooleannoDefault true: the caption/face overlap pass.

Get an export check

Read an export check that check_studio_export started (the same answer when it is done).

ParameterTypeRequiredDescription
check_job_idstringyesThe check_job_id check_studio_export returned.

Get a Story split job

Read a Story split job that split_export_for_stories started. While it runs, this returns its status — call it again. When it is done, it registers each part as media and returns the parts in order with their media ids (the same answer split_export_for_stories gives). Safe to call again: a part keeps its media_id.

ParameterTypeRequiredDescription
split_job_idstringyesThe split_job_id that split_export_for_stories returned.

List Templates

List all slide templates available in dfl-slide-templates (reads registry.json from GitHub).

Takes no parameters.

Search Templates

Find the best slide templates for an intent. Returns a RANKED shortlist (top-N) instead of the full list — give a natural-language intent (e.g. “compare two options head-to-head”, “mostrar uma captura de tela”, “one big hero number”) and get back the templates whose registry “when_to_use”/tags/media_profile best match, each with a relevance score and a one-line reason. Use this to PICK a template, then call get_template(id) for its slots. Deterministic + read-only (no auth needed).

ParameterTypeRequiredDescription
intentstringyesNatural-language description of what the slide should do (English or Portuguese), e.g. “comparar duas opções”, “show a screenshot”, “single big statistic”.
limitnumbernoMax templates to return in the shortlist (default 5).
canvasstringnoOnly templates that declare this design canvas (e.g. “social-portrait”). Omit for no canvas filter.
project_idstringnoFilter by this project’s canvas (its orientation). Ignored when canvas is given.

Get Template

Fetch all source files (HTML landscape, HTML portrait, CSS landscape, CSS portrait, config YAML) for a specific template.

ParameterTypeRequiredDescription
idstringyesTemplate ID (e.g. “title”, “bullet-list”, “two-column”)

Update Template

Open a ready-to-merge PR in dfl-slide-templates with updated HTML/CSS and optional config YAML for a template. All provided fields are written in a single commit. Returns the PR URL. registry.json is edited as a MERGE: any registry_* field you omit keeps its current value, so a version bump can no longer erase the discoverability metadata search_templates ranks on. Pass null to delete a field. CREATING a template (an id not yet in registry.json) requires registry_version, registry_name, registry_when_to_use, registry_media_profile and registry_tags, so the new template is findable from the moment it merges.

ParameterTypeRequiredDescription
idstringyesTemplate ID to update (e.g. “title”)
descriptionstringyesHuman-readable description of the change (used as PR/commit title suffix)
html_landscapestringyesNew landscape.html content
css_landscapestringyesNew landscape.css content
html_portraitstringyesNew portrait.html content
css_portraitstringyesNew portrait.css content
config_yamlstringnoNew config.yaml content (optional — skip to leave unchanged)
registry_versionstringnoBump the registry version (e.g. “1.1.0”) — omit to leave unchanged. REQUIRED when creating a new template.
registry_categorystringnoUpdate the registry category (content | layout | data | …) — omit to leave unchanged. Defaults to “content” on create only.
registry_namestringnoHuman display name, e.g. “Title Slide”. search_templates ranks on it. Omit to leave unchanged; null to delete. REQUIRED when creating.
registry_when_to_usestringnoWhen an authoring agent SHOULD reach for this template — the highest-signal ranking field. Omit to leave unchanged; null to delete. REQUIRED when creating.
registry_avoid_whenstringnoAnti-patterns: when NOT to use this template. Omit to leave unchanged; null to delete.
registry_media_profileenumnoWhat kind of content this template is shaped for. Omit to leave unchanged; null to delete. REQUIRED when creating. One of: text-heavy, balanced, image-first, image-only, video, data, code, diagram.
registry_text_densityenumnoHow much copy the layout carries. Omit to leave unchanged; null to delete. One of: none, low, medium, high.
registry_layoutstringnoOne-line shape description, e.g. “centered big headline + subtitle, no media”. Omit to leave unchanged; null to delete.
registry_tagsstring[]noFree keywords for matching — the heaviest-weighted ranking field. Replaces the whole list (never merged element-wise). Omit to leave unchanged; null to delete. REQUIRED when creating.

List Themes

List the CSS themes that actually exist in dfl-slide-templates at the pinned revision. The set is DERIVED from the repo (registry.json themes[]), so a theme added upstream appears here with no code change. Each entry reports whether its stylesheet resolved, plus its display name and light/dark mode when declared. The response names the commit it was read from and flags a degraded read.

Takes no parameters.

Get Theme

Fetch the CSS source for a specific theme.

ParameterTypeRequiredDescription
idstringyesTheme ID as reported by list_themes (e.g. “default”, “devfellowship”, “itera”, “revera”)

Update Theme

Open a ready-to-merge PR in dfl-slide-templates with updated CSS for a theme. Returns the PR URL. The PR also REGISTERS the theme in registry.json’s themes[] — the array themes_doc calls the source of truth and list_themes reads — so a theme published here is discoverable instead of being a stylesheet nothing lists. Registering a NEW theme requires theme_name and theme_mode: neither a brand’s display name nor its light/dark mode can be inferred, and a guess would become the source of truth. A new theme also needs its forbidden-colour contract in scripts/theme.config.json, or lint:css fails — that file is human-gated, so a brand-new theme cannot merge unattended.

ParameterTypeRequiredDescription
idstringyesTheme ID to update, as reported by list_themes (e.g. “devfellowship”, “itera”)
descriptionstringyesHuman-readable description of the change (used as PR/commit title suffix)
cssstringyesNew CSS content for the theme file
theme_namestringnoDisplay name for the registry themes[] entry, e.g. “Itera”. Omit to leave an existing theme unchanged. REQUIRED when registering a new theme.
theme_modeenumnoAdvisory light/dark hint for the host chrome around the slide (the slide always paints its own surface). Omit to leave an existing theme unchanged. REQUIRED when registering a new theme. One of: light, dark.

Render Slide Image

Render one stored slide to a PNG and return its URL. Wraps the deterministic headless capture in dfl-render with the CALLING user’s own credentials. Returns the dfl-slide-templates commit revision the template was read at AND a composition fingerprint of the deck state the derived slide index came from — together those two reproduce the render. canvas is optional; a canvas the template does not declare is REFUSED and no image is produced (never a fallback to the nearest canvas).

ParameterTypeRequiredDescription
slide_idstringyesUUID of the slide to render
canvasstringnoDesign canvas id, e.g. “landscape”, “portrait”, “social-portrait”. Omit to use the parent project’s orientation. Validated against the declared canvas contract AND against the canvases the slide’s template ships; an undeclared canvas errors and produces no image.

Render Composition Images

Render every surviving slide of a composition to a PNG and return the URLs in order_index order — the batch form of render_slide_image, and the tool a carousel needs. Soft-deleted slides are skipped. Captures run with bounded concurrency. Returns the dfl-slide-templates commit revision and ONE deck-level composition fingerprint shared by every image. canvas is optional; a canvas any template in the deck does not declare is REFUSED before anything is captured.

ParameterTypeRequiredDescription
composition_idstringyesUUID of the composition to render
canvasstringnoDesign canvas id, e.g. “landscape”, “portrait”, “social-portrait”. Omit to use each slide’s parent project orientation. Every distinct template in the deck must declare it, or the whole call errors and no image is produced.

Capture Cover Slide

Render the composition’s cover slide (template_data.is_cover, set by set_cover_slide) to a PNG for the deck’s video thumbnail, and return its image_url. Errors clearly when the composition has no cover slide. This does NOT write projects.thumbnail_url — no tool in dfl-mcp-studio does yet — so persist the returned image_url yourself once such a tool exists.

ParameterTypeRequiredDescription
composition_idstringyesUUID of the composition whose cover slide to capture

Generate Lesson From Tutor Profile

Author a NEW lesson topic in a specific tutor’s proven pedagogical style. Loads that tutor’s stored pedagogy profile (lms.tutor_profiles) and returns a Studio-importable GENERATION SPEC: a typology-biased slide plan (template picks ranked by the professor’s dominant lesson-type via search_templates’ ranker), the professor’s SIGNATURE as few-shot exemplars (verbatim metaphors/openers/cotidiano anchors), and amplify/reduce directives (amplify high-consistency moves, reduce vícios). Deterministic — the tool does NOT call an LLM; the calling agent expands each suggested slide into content, few-shotting the exemplars and honoring the directives. The rubric (HALF A) is never used for generation (de-circularization).

ParameterTypeRequiredDescription
tutor_idstringyeswork.members.id of the tutor whose stored pedagogy profile to author in (matches lms.tutor_profiles.member_id).
topicstringyesThe new lesson topic to author in that tutor’s style, e.g. “Introdução a APIs REST”.
versionstringnoProfile version to load. Defaults to the newest (most recently updated) profile for the tutor.
n_slidesnumbernoTarget slide count / number of template picks (default 8).
canvasstringnoOnly pick templates that declare this design canvas (e.g. “social-portrait”).
project_idstringnoPick only templates that fit this project’s canvas. Ignored when canvas is given.