cueframe
Turn footage into finished, production-quality video over MCP.
What it can do
- Create Composition: Start the async Director compose workflow: it authors an ensemble of full composition candidates, verifies each, and saves the winner to the project. Body must set EXACTLY ONE sour
- Apply Composition: Atomically apply an array of CompositionOps to a project composition. All ops succeed or none do (transactional). Pass `if_match` for optimistic-concurrency control (409 stale_etag
- Validate Composition: Dry-run a full Composition object against the SAME pure, DB-free authoring guards saveComposition enforces, WITHOUT persisting it (no project needed). Checks: wire shape (Zod), p
What data it sees
Do you need an account
No: the server works without sign-in
Turn footage into finished, production-quality video over MCP. Content-aware reframing, captions behind the subject, and motion graphics, with free previews and keyless pay-per-render.
How to connect
How to connect cueframe to Claude
The server address to paste into Claude settings:
https://cueframe--cueframe.run.tools
- Open Claude (claude.ai or the desktop app).
- Go to Settings → Connectors.
- Click Add custom connector.
- Paste the server address copied below into Remote MCP server URL and click Add.
- In a chat, click + → Connectors and switch the new connector on.
Custom connectors are available on Free, Pro, Max, Team and Enterprise plans (Free is limited to one). On Team and Enterprise an organization Owner adds the connector first under Organization settings → Connectors.
Authorization
After you click Add, a sign-in window for the service opens. Sign in with your own account and approve access. Claude never sees your password.
How to connect cueframe to ChatGPT
The server address to paste into ChatGPT settings:
https://cueframe--cueframe.run.tools
- Open ChatGPT in a browser (chatgpt.com). A Plus, Pro, Business, Enterprise or Edu plan is required.
- Turn on developer mode once: Settings → Apps → Advanced settings → Developer mode.
- Open Settings → Connectors and click Create.
- Fill in the form: Name (anything), Description (one line about what the service does), MCP server URL (copy it below).
- Under Authentication choose OAuth if the service requires sign-in, otherwise None. Click Create.
- In a new chat open + → Apps/Connectors and enable the connector.
OpenAI has renamed this section before (Connectors → Apps/Plugins). If the label differs, search settings for "developer mode". On Business/Enterprise workspaces an admin must allow custom connectors first.
Authorization
On first use ChatGPT opens the service's sign-in window. Sign in and approve access.
How to connect cueframe to Cursor
The server address to paste into Cursor settings:
https://cueframe--cueframe.run.tools
Fastest: click Open in Cursor below and confirm the prompt.
Manually:
- In Cursor open Settings → Cursor Settings → MCP and click Add new global MCP server.
- Paste the JSON copied below into
~/.cursor/mcp.json(per project:.cursor/mcp.jsonin the repo root). - Save the file. The server appears in the MCP list; authorize it there if asked.
Authorization
If the service needs sign-in, an authorize button appears next to the server in the MCP list.
Server tool list (44)
Raw names from tools/list. Only developers need these.
| create_composition | Start the async Director compose workflow: it authors an ensemble of full composition candidates, verifies each, and saves the winner to the project. Body must set EXACTLY ONE source (mutually exclusive): `fromComposition: true` — author from the project's CURRENT composition (the GENERAL path: first seed it with apply_composition to add your media clips = your intent, then compose); or `suggestionId` — author from an AI clip suggestion (the CLIPPING path, paired with trigger_suggestions). Returns { composeJobId, workflowId, status, streamUrl }. Then call wait_job(kind="compose", id=composeJobId, projectId=...) to block until the winner is selected. |
| apply_composition | Atomically apply an array of CompositionOps to a project composition. All ops succeed or none do (transactional). Pass `if_match` for optimistic-concurrency control (409 stale_etag on mismatch) and `dry_run: true` to validate the batch without persisting. Use this to seed a composition (add media clips) or make targeted edits (set a crop intent, add a caption, reorder clips, set the format). Brand is NOT set here — it lives on the project (create_project / PATCH /projects/:id with brandKitId). |
| validate_composition | Dry-run a full Composition object against the SAME pure, DB-free authoring guards saveComposition enforces, WITHOUT persisting it (no project needed). Checks: wire shape (Zod), primitive membership (unknown_primitive / custom_missing_sourcecode), required-param shape (invalid_primitive_params), clip↔track family (clip_source_track_mismatch), and reframe coverage (reframe_coverage_gap). Always returns ONE shape — 200 { valid, errors } — for BOTH malformed wire shapes and deep-invariant failures (so branch on `valid`, not on HTTP status); errors[] lists every problem at once as a sanitized envelope { code, stage, category, retryable, message, fix } you can fix before composing/rendering. `valid:true` means save-safe EXCEPT for the two genuinely DB-bound checks that can only run at save/render: trim-bounds (needs the media duration) and image/asset-on-a-video-track (needs the media kind). Pass the Composition as `body`. |
| create_render | Queue an MP4 render of a project's composition. Returns { id (renderId), status, warnings? } — `warnings[]` carries non-fatal advisories (omitted when none). The render themes from the project's brand kit (bind one via create_project / PATCH /projects/:id with brandKitId; without one the output is unbranded). Then call wait_job(kind="render", id=renderId, projectId=...) to block until the file is ready and get the download URL, or register a webhook (create_webhook on render.complete). |
| open_session | Open a warm per-tenant render/compose box for interactive work and return its sessionId. The box stays warm between calls, so it is the fast path for an author→preview→fix loop: pass the returned sessionId to preview_component (one graphic still), render_frame (a whole-composition still), and verify_composition (judge scores) — all render in this same box. CLOSE IT with close_session when done (it holds a billable warm box; it auto-stops at the idle TTL, but closing promptly is cheaper). |
| render_frame | Render still(s) of the WHOLE composition at the given timestamps inside an open warm box, returning presigned PNG(s) — the live full-composition preview. Needs a sessionId from open_session. `body.composition` is the composition to render and `body.timestamps` the seconds to sample. (One component instead of the whole composition? use preview_component.) |
| preview_component | Queue a still render of ONE org-installed component inside an open warm box — ASYNC: returns a jobId immediately; then wait_job(kind="preview", id=jobId) for the presigned PNG. The author→preview→fix loop for a graphic. Needs a sessionId from open_session. `body.componentId` selects the component; pass `body.params` to render it with props. Failures land on the job terminal with typed codes: component_does_not_compile / component_runtime_error carry the sanitized author-fixable message — read it, fix the tsxSource via create_component, and re-preview. Idempotent per (componentId, params, dims). |
| verify_composition | Queue the server-side judge for a compose-session composition — ASYNC: returns a verifyJobId immediately (the judge takes minutes: it samples eval beats, renders them in the warm box, and grades editorial/spatial/brand/caption). Then wait_job(kind="verify", id=verifyJobId) for per-criterion scores 0–10, a weighted composite, and a worst-first critique; fix the lowest criterion via apply_composition and re-verify. Idempotent per composition state (same composition → same job while it runs). Do NOT apply/render on this session while its verify job runs — the version gate fails the job loud. |
| commit_session | Promote the session scratch onto the project's active composition and discard the scratch. Returns the new active etag. |
| consult | Ask the Director for editing advice on the project's CURRENT composition — it NEVER writes your composition (you stay the decider). Async: returns a job; wait_job for it, then read `advisoryResult` from the job detail. `body.mode`: 'advise' = the planner agent (fast, ~30s-2min) returns a `plan` (framing/cuts/captions/graphics intents) as GUIDANCE you re-author from — no grounded scores, nothing to select; 'amend' (default) = the full author→judge→refine loop (minutes) returns a graded `assessment` + a fix candidate you ADOPT via the compose `select` endpoint with its candidateIndex. ('explore' is coming soon → use amend.) `body.intent` = your goal in plain English; optional `body.focus` (editorial|spatial|brand|caption); `body.ifMatch` binds to your current etag. Use advise for a fast directional nudge, amend for a grounded, adoptable fix. |
| resume_compose | Answer a compose checkpoint that wait_job reported as awaiting_input. `body.stage` = the held stage (content|layout|motion); `body.decision`: 'approve' (proceed as-is), 'revise' (proceed with `body.edits` — your edited stage artifact), or 'reject' (abandon the compose). `body.version` MUST echo the version wait_job reported (a stale version is a 409 — re-read the job detail). On approve/revise the Director rehydrates its warm box from the persisted composition and continues; then wait_job again to reach the next checkpoint or the winner. Only valid while the job is awaiting_input. |
| close_session | Tear down a warm compose session and free the box. Call this when you finish an interactive open_session → preview/render/verify loop — it stops the billable warm box immediately (it would otherwise auto-stop only at the idle TTL). |
| create_component | Create and store a custom graphic component (a reusable overlay/title/lower-third etc.) for the tenant. Returns the ComponentResponse (id + metadata) you can then reference from a composition. No project arg — components are tenant-scoped. |
| list_components | DISCOVER WHAT TO BUILD WITH — list the component catalog: the FULL built-in primitive catalog (87 primitives + 7 scenes: lower-third, cinematic-title, color-grade, product-launch-trailer, …) PLUS your org-installed components. Do this BEFORE hardcoding hex/positions or authoring a custom graphic — a built-in probably already exists. Narrow with filters: `category` (text|effects|transitions|layouts|backgrounds|ui-blocks|scenes), `kind` (component|transition|scene), `tier` (recommended|standard|niche), `useCase` (intro|outro|full-scene|title-over-footage|transition|background|grade|emphasis|explainer|social-cut), `mood`, or free-text `q`/`intent` (e.g. intent='intro'). When any filter is set, results are recommended-tier-first. With NO `limit` the whole filtered catalog returns in one page. Each entry carries a `placement` hint = exactly how to author it via apply_composition ({kind:'overlay'|'effect', primitiveId} for built-ins; {kind:'component', componentId} for installed), `tier`/`useCase` to pick by, and `fixedCopy` (on-screen strings it BAKES that no param changes — fork via get_component_source to edit). Read cueframe://component/{id} for the full prop schema. |
| get_component_source | Read a component's editable TSX source. Pass a BUILT-IN primitive id (e.g. "cinematic-title", "lower-third", "kinetic-type") to get it as a SINGLE self-contained module — the server-native FORK path: edit the returned tsxSource, then create_component it back to own a customized copy. Also returns an org-installed component's own authored source. Returns { id, name, tsxSource, origin, ejectedFrom, propSchema, … }; 404 if the id is neither a known primitive nor an installed component carrying source. |
| get_media_context | Fetch the authoring context for a media item: detected faces (subject boxes per frame) and the transcript. Use this before composing/applying so your crop intents, captions, and timing line up with what is actually in the footage. |
| list_media | List the videos in your CueFrame library. Returns media item IDs and metadata. Use the ID with get_media_context, trigger_suggestions, or as the source for a composition. |
| create_project | Create a new project — the unit you compose and render into. create_composition / apply_composition / create_render / open_session all require a projectId; this mints one. Pass name (required) plus optionally a format (aspectRatio/fps/resolution) and brandKitId. brandKitId binds a brand kit to the PROJECT (pass the kitId from create_brand_kit): colors/fonts/motion theme every render of this project automatically (omit it and the output is unbranded). Bind or change it later via PATCH /projects/:id. Returns the ProjectResponse (id + metadata). |
| new_video | Create a NEW composition (a new video) inside a project — a project is a workspace that holds many videos. Returns { compositionId, format, etag }. Pass an optional name and format (aspectRatio/fps/resolution); format defaults to the project's compositionFormat. The first composition becomes the project's active default. To author it, seed with apply_composition then create_composition({ fromComposition: true }); to render a specific one, pass its compositionId to create_render. (For a reframed sibling in another aspect, use derive_composition.) |
| list_compositions | List every composition (video) in a project, with its format and which one is active. Use a returned compositionId with create_render(compositionId=...) to render a specific video, or apply_composition to edit it. |
| derive_composition | Derive a reframed SIBLING composition for a different aspect ratio (the agent-native Auto-Reframe / Magic-Resize). Pass projectId + body { fromCompositionId, targetFormat: { aspectRatio, fps?, resolution? } }. CueFrame copies the source composition's authored intent and content-aware-reframes every clip for the new aspect (point/face/active-speaker focus), then saves a new independent composition in the same project. Returns { compositionId } — render it with create_render, or refine it with apply_composition. Use this to make 9:16 + 1:1 + 4:5 cuts from one 16:9 edit. (To only change resolution within the SAME aspect, pass create_render.format instead — that is a scale-only export override, not a new composition.) |
| import_media | Import a media item into your library from a public URL — the agent-friendly ingest path (single call, no upload protocol). Pass url (https) + filename (+ optional contentType). Returns { id, status:"importing" }. Processing is async: poll list_media for processingStatus, or register a webhook (create_webhook on media.ready / media.failed). Use the returned id with get_media_context, trigger_suggestions, or as a composition source. |
| generate_media | Generate a media item from a prompt (generator: manim | text-to-video | text-to-image | image-to-video | auto). Returns { id, status:"generating", estimateUsd? }; returns 402 if the estimate exceeds the per-call ceiling. Async — poll list_media or use a webhook (media.ready / media.failed). Use the returned id like any other media item. |
| search_resources | Search licensed STOCK media to compose with — video/photo (Pexels + Pixabay fallback), sfx (Freesound, CC0-only), gif (Klipy). Pass query (+ optional kind [default video], provider pin, limit ≤50). Returns ranked candidates, each with an opaque candidateId + attribution (author, assetUrl) + license. FREE + rate-limited + 7-day cached — reach for this before generate_media when real footage beats synthesis. Import a chosen candidate into a project with import_resource. Candidates expire after 7 days (re-search to refresh a candidateId). |
| search_fonts | Search web font families by name/style — returns family names to use in composition/component params. Pass query (+ optional perPage ≤50). Read-only + free; surfaces the Google Fonts catalog (family + category + variants + css2 stylesheet URL). An empty result set means no match (or the upstream catalog was unavailable). |
| import_resource | Import a stock candidate (candidateId from search_resources) into a project as an ORG-OWNED media item — provider/externalId/license provenance preserved, bytes deduped across orgs. Pass projectId + candidateId. Returns { id, status:"importing"|"ready" }; processing is async — poll list_media / get_media_context, then use the returned id as a composition source. Gated on the resource_import plan feature (402 billing_required if your plan lacks it). 422 candidate_mismatch if the candidate expired — re-run search_resources. |
| trigger_suggestions | Run AI clip-suggestion analysis on a long-form media item — the CLIPPING path (find the best short-form moments, à la CrowdCut). Returns { workflowId, status }; then wait_job(kind="suggestions", id=mediaItemId) and pass a returned suggestion id to create_composition({ suggestionId }). NOTE: clipping vertical, NOT the general path — to compose from your own media, seed via apply_composition then create_composition({ fromComposition: true }). |
| create_webhook | Register a webhook so long-running jobs notify you instead of blocking on wait_job — the right pattern for fan-out/async agents. Pass url + events (e.g. render.complete, render.failed, compose.complete, media.ready). DELIVERY: a POST { event, timestamp, projectId?, data } with Standard-Webhooks headers (webhook-id, webhook-timestamp, webhook-signature: v1,<base64>); the secret (whsec_<base64>) is returned ONCE — verify the RAW body with new Webhook(secret).verify(rawBody, headers). ACTIVATION is synchronous: CueFrame POSTs a signed webhook.verify challenge and your endpoint must reply 2xx echoing data.token — otherwise it returns status:"pending" and receives NO events (recover with verify_webhook, no delete/recreate). Read the cueframe://webhook-events resource (or GET /webhooks/events) for the full contract + every event's data shape BEFORE building your receiver. |
| verify_webhook | Re-run ownership verification on a PENDING webhook — one whose endpoint did not echo the challenge at create time (check its verification.reason: token_not_echoed / non_2xx_<code> / unreachable / ssrf_blocked). After you fix the receiver to reply 2xx echoing data.token, call this to re-send the signed webhook.verify challenge and flip it active IN PLACE (id + secret preserved — no delete/recreate). Returns { id, status:"active"|"pending", verification? }. An already-active webhook returns active unchanged. |
| register_critic | Register a custom eval into the compose critic panel. Three tiers: rules (declarative constraints — keep-out regions, required elements, max overlays, brand colors), rubric (a text criterion scored by the judge as an extra column), or evaluator (your HTTPS webhook — CueFrame POSTs the composition+stills+facts, you return { score, critique }). A low client score drives a refine like a built-in criterion. You see your eval output + our scores, never our rubric. Re-registering the same criticId upserts. Evaluator tier returns a signing secret ONCE. |
| update_project | Update a project's mutable fields (e.g. name, format, brandKitId). Pass projectId + the changed fields as `body`. Setting brandKitId binds/changes the project brand kit so every render of this project themes from it. Returns the updated ProjectResponse. |
| delete_project | Permanently delete a project you created (cleanup). Removes its composition and render history. Irreversible — only call on a project you own and no longer need. |
| delete_media | Permanently delete a media item from your library (cleanup). Irreversible. Compositions referencing it will fail to render — only delete media no composition still needs. |
| update_component | Update an authored component's tsxSource/metadata IN PLACE (same id) — the correct verb for the author→preview→fix loop (preview_component returns the compiler error; fix the source here and re-preview, instead of creating a new component each iteration). Pass componentId + the changed fields as `body`. |
| update_webhook | Update a webhook subscription (its url or event list). Pass webhookId + changed fields as `body`. The signing secret is NOT rotated here. |
| delete_webhook | Delete a webhook subscription (stop receiving its deliveries). Irreversible — the signing secret is invalidated. |
| cancel_render | Cancel an in-flight render (stop a runaway/queued job). Needs projectId + renderId (from create_render). No-op if already terminal. |
| retry_render | Re-render an existing render job's FROZEN composition snapshot — it inherits that job's exact composition/intent/priority and queues a fresh renderJob; it does NOT re-read the project's current composition. So it only helps a TRANSIENT failure (the failed render's `retryable:true` / `category:"transient"` — a flaky pipeline/infra fault): re-attempting the same snapshot may succeed. For a DETERMINISTIC failure (`retryable:false` / `category:"authoring"` — a bad param, an unresolvable media/brand ref), retry_render reproduces the SAME failure: FIX the composition first via apply_composition, then create_render (a retry alone is futile). Check `retryable` on getRender / wait_job to pick the path. Returns the new render; then wait_job(kind="render", id=<new id>). |
| get_account | ORIENT FIRST — show THIS key's account before you spend a metered call: the org it is bound to, the current plan/tier, a per-feature entitlements map (included? + remaining/limit for metered features), and per-call cost ceilings. Use it to see what your plan/credits allow so create_render / create_composition / generate_media / create_brand_kit do not fail mid-workflow with 402 billing_required — entitlements.exports gates create_render AND generate_media output, entitlements.compose gates create_composition, entitlements.brand_kits gates create_brand_kit, and costCeilings.generateMediaUsdPerCall is the generate_media per-call USD cap. No args — identity is resolved from your key (never echoed from input). |
| list_plans | List the plan catalog (tiers, intervals, product ids, prices) BEFORE a checkout, so create_checkout requests a real {plan, interval} instead of guessing an id. No args — reads the platform catalog. |
| create_checkout | Upgrade the CURRENT org. Body { plan: "pro"|"business"|"studio", interval: "month"|"year" } → returns { url }: a hosted Stripe checkout page to open in a browser and confirm payment. Always a URL — an existing saved card is NEVER charged silently; the human confirms on Stripe. interval:"year" is annual. The org is resolved from your key, never the body. Requires the billing:write scope. Discover valid plans with list_plans. |
| create_brand_kit | Create or UPSERT a brand kit (re-POSTing the same kitId updates it in place — this is also the brand-kit UPDATE path). Sets fonts/colors/logo. This is brand step 0 of the workflow: author the kit, then bind it to a PROJECT so colors/fonts/motion theme the output AUTOMATICALLY — pass its kitId as brandKitId to create_project, or attach it to an existing project via PATCH /projects/:id. Without a bound kit, create_render produces UNBRANDED output. Tenant-scoped (no project arg). |
| extract_brand_kit | Queue brand-token extraction from a website URL — ASYNC: returns a jobId immediately; then wait_job(kind="brand_kit_extract", id=jobId) for the tokens (colors/fonts/title/description, fetched + regex-parsed from the site's HTML/CSS — NOT a screenshot/vision pass). It does NOT persist — review the tokens, then call create_brand_kit to save a kit. A blocked (private/SSRF), non-2xx, non-HTML, or slow (>20s) URL degrades to EMPTY token arrays (a succeeded job) rather than erroring — JS-rendered SPAs typically extract empty; author the kit manually from the compiled CSS in that case. |
| wait_job | Wait for a long-running job and return the resolved result — a BOUNDED long-poll. Polls the job status (render / compose / clip-suggestions / verify) and blocks up to the transport window (30s on the hosted transport); if the job is still running at the window it returns a `Still running` continuation — call wait_job again with the same arguments to keep waiting (the job keeps executing server-side; nothing is lost between calls). Call this right after create_composition / create_render / trigger_suggestions / verify_composition. For event-driven fan-out, create_webhook delivers the terminal instead. |