Tools
Framehood exposes a small, functional toolset over MCP. You think in outcomes; the server picks the model. Every generation tool requires an out filename.
Discover at runtime
Use models(list) to browse reachable models and models(guide, model=…) for a model's parameters and prompting tips (or GET /v1/models/{kind} for the raw input schema).
What the MCP server exposes
The core generation and account tools:
image · video · audio · qa · files · billing · org · get_status
Two helpers — browse the catalog and send feedback:
models · submit_feedback
Three more manage your workspace:
library · project · api_keys
Actors temporarily disabled
The actor tool and the actor-dependent actions (image(actor_sheet), video(scene), and actor_id routing on image / video) are temporarily disabled while we rework them. The rest of the toolset is unaffected.
The CLI and the REST API wrap these same tools and add a few surface-only endpoints (raw model access, uploads, and the Stripe card/portal billing flows) — see CLI / REST surface below.
ChatGPT clients see live status text
image, video, audio, and qa — the tools that submit a generation job — carry status strings (e.g. "Generating image…" while running, "Image job submitted — poll get_status" once the call settles) that ChatGPT's MCP client shows while the call is in flight. These jobs are usually still processing when the call settles, so the status text reflects submission, not a finished asset. Other clients are unaffected; this is purely a ChatGPT UI nicety.
Result shape
Every MCP tool result carries a small, predictable envelope on top of its own fields: ok (boolean), result_kind ("job" for an async generation still in flight or its terminal outcome, "result" for an immediate data/management reply, or "error"), plus tool and action identifying the call. Nothing about a tool's existing response changes shape or gets removed — the envelope is purely additive, so code written against the old response keeps working unchanged.
Job-shaped results (image, video, audio, qa, and get_status for a single job — the default status action, or cancel) also carry artifacts[] — a normalized [{url, type?}, ...] list of the job's output files — alongside the original outputs map, which is unchanged. Use whichever is more convenient: outputs.image_url etc. for the legacy shape, or artifacts[] to iterate output files generically regardless of media type.
get_status's batch (job_ids) and list actions are result_kind: "result", not "job" — the envelope describes the call as a whole (summary-only, no top-level artifacts[]); the individual entries inside jobs[] keep their plain {job_id, status, ...} shape (and their own outputs, if terminal).
Three optional steering fields may also appear (each omitted entirely when there is nothing to say):
note(string) — advice about this specific result, e.g. "prompt was truncated" or "URL expires in 24h".next_step(object) — the recommended next call, machine-readable:{tool, action?, args?, why}.argscarries concrete values (e.g.{"job_id": "job_abc123"}) you can pass verbatim. Non-terminal job results carry aget_statuspoll here; some errors carry a recovery call on top of the prosehints. Breaking (server 2.12.0):next_stepused to be a prose string on submit/poll replies — it is now always this object on MCP.notifications[]({type, text, id?, url?}, max 3) — global account-level notices, independent of the tool called. Currently always absent; the channel ships ahead of its producers.
MCP tools
What you can pass as a media reference
Every media input — image_url, video_url, audio, reference_images, clips, tracks, vo, music, and the like across image / video / audio / qa — accepts any of:
- A cdn URL returned by an earlier job or by
files(upload / import_remote)— public or private. A privatecdn.framehood.ai/…URL works as a reference as-is — you do not need topublishit first. (publishonly mints a public, shareable/downloadable URL; it is not required to reuse a file in another job.) - A library
asset_id— the id of any item in your library (fromlibrary(list), a generated result, orfiles(import_to_library)). Pass it straight in as theimage_url/video_url/ etc. and it resolves to the asset automatically — no URL lookup, no publish. Library items are shared across your organization, so a teammate'sasset_idworks too; an id from a different organization (or a deleted item) returns a clear "not found" error. - A data-URI (
data:<mime>;base64,…, up to 25 MB) — a local file inlined into the call; it is uploaded for you and the resulting URL is used. Best for small files (e.g. a downscaled reference image): some clients truncate a large inline payload in transit — for larger or original-quality files usefiles(create_upload)+ PUT instead.
The asset_id and data-URI shortcuts are resolved by the image / video / audio / qa tools. If you call the REST job API directly (POST /v1/jobs/{kind}), pass a media URL (an asset's url) rather than a bare asset_id or data-URI.
Calling from ChatGPT? A chat attachment does not populate a tool's media field (these tools don't declare openai/fileParams) — pass a URL or asset_id instead, bringing a file in first via files(import_remote) / files(upload). See ChatGPT / OpenAI MCP compatibility.
image
Create and edit still images (and image→video).
| Action | Key arguments | Does |
|---|---|---|
create | prompt, out, tier?, format? | text → image |
edit | image_url or reference_images[], prompt, out | modify an image. Pass up to 10 references as reference_images and cite them in the prompt as @Image1…@ImageN — for identity work a headshot plus a full-body shot holds a face far better than one image. image_url is the single-image shorthand |
upscale | image_url, out | higher resolution |
animate | image_url, out, duration? | image → short video |
actor_sheet | actor_id, out_prefix, variations? | multi-angle character sheet |
tier: draft < fine < photo. actor_id (act_…) routes create/animate through an actor's LoRA. Some models are reachable only by name — e.g. Nano Banana Pro: image(create, model="nano_banana", …) (it is not a tier); see the models tool.
seed? (any action): an optional integer for reproducible output — reuse the same seed (with the same prompt/params) to get consistent results. Ignored, with no error, by models that don't support seeding.
video
Generate, edit, and compose video.
| Action | Key arguments | Does |
|---|---|---|
create | prompt, out, reference_images?, duration?, format? | text → video (no actor needed) |
edit | video_url, prompt, out, duration? | restyle / edit footage |
edit_ref | video_url, prompt, reference_images, out | edit guided by reference photos (@Image1…) |
swap | video_url, image_url, swap_mode, out | replace a person, object, or background |
lipsync | video_url, audio_url, out | match lips to an audio track |
captions | video_url, out | burn in auto subtitles |
upscale | video_url, out | higher resolution |
assemble | clips, out, audio bed (music?, vo?, vo_offset_sec?, vo_cues?, sfx_ambient? …) | combine clips + transitions and mix an audio bed into one video |
mix_audio | video_url, tracks, out | overlay VO / music / SFX onto a video |
blur | video_url, out, region?, sigma?, windows? | blur/redact a region (or the whole frame), optionally only during time windows |
watermark | video_url, image_url, out, position?, x?, y?, margin?, opacity?, scale?, window? | overlay a logo/watermark PNG at a corner (or explicit x/y), for the whole clip or a time window |
scene | actor_id, scene_prompt, out | actor full-scene composite (image → motion → optional speech) |
swap_mode: person · object · background. In edit_ref, the images you pass in reference_images are cited in the prompt as @Image1…@Image4. scene routes through an actor (act_…) and can auto-script a spoken line from the actor's personality.
For MORE than one narration line, assemble accepts vo_cues ([{audio_url, start_sec, level?}, …]) — each line is placed at its own timecode in one call, INSTEAD of vo/vo_offset_sec (passing both is an error). mix_audio tracks accepts the same cue shape: a plain URL, or a {audio_url, start_sec?, level?, label?} object placed at a timecode. Levels are static per cue; pass duck: true to auto-duck the music bed under the voice — assemble keys it off the clip audio + VO, and mix_audio off tracks labeled "music" and "vo".
seed? (any action): an optional integer for reproducible output — reuse the same seed (with the same prompt/params) to get consistent results. Ignored, with no error, by models that don't support seeding.
audio
| Action | Key arguments | Does |
|---|---|---|
speak | text, out, voice? | text → voice |
sfx | prompt, out | sound effects |
music | prompt, out, lyrics? | music / ambient |
mix | tracks (2+; or 1+ with music?/music_level?), out | blend audio files — flat mix, or a music bed ducked under the voice |
trim | audio, start_sec?, duration_sec?, out | cut an audio file to a window |
concat | tracks, out | join audio in sequence |
To overlay audio onto a video, use video(mix_audio).
qa
Inspect media for problems. Returns a pass/fail report.
| Action | Key arguments | Does |
|---|---|---|
full | video, plan? | run nine checks at once |
person | image1, image2 | same person in two images? |
voice | audio | consistent voice? |
scene | video, plan | does the video match the scene plan? |
transcript | video|audio, expected_text?, language? | transcribe media (video OR audio) with timecoded segments; if expected_text is given, also check the spoken words match |
image | image_url, description | does the image match the description? |
describe | video, fps?, focus? | timecoded scene-by-scene description (what happens, speech, sounds, music); returns a job_id — poll get_status |
Omit expected_text on transcript for transcription-only mode (no compare, verdict PASS); the result carries actual_transcript, duration_sec, and timecoded segments either way.
files
Manage your storage.
| Action | Key arguments | Does |
|---|---|---|
list | prefix?, cursor? | list your stored files |
upload | filename + url | data | bring a file into your storage — from a hosted URL, or a LOCAL file's raw bytes via data (data-URI / base64, ≤25 MB) |
create_upload | filename, content_type | get a token + upload_url to PUT a LARGE / original-quality local file directly (no 25 MB cap) |
import_remote | download_url (or url) | pull a remote file (e.g. a file an assistant/chat app handed you as a URL) into your storage in one call — no manual download/re-upload. Returns a Framehood url you can pass straight into image/video/audio/qa. |
delete | filename | remove a file |
publish | filename | make a file public, returns a public_url |
unpublish | filename | make a published file private again |
download | filename | get a usable URL to fetch the bytes |
import_to_library | filename (the uploaded key), name?, project_id? | mint a library asset (asset_id) from one of your uploaded files, optionally assigning it to a project — so uploads can be organized/assigned like generated assets |
You don't need to publish to reuse a file
publish is only for sharing a file publicly — it returns a public_url anyone can open or download. To use a file as a reference in another job (image_url, video_url, …) you do not publish it: pass its private cdn URL, or its library asset_id, directly (see What you can pass as a media reference above).
Bringing in a file from another app
If another app or assistant hands you a file only as a URL (a chat attachment, a generated image it made elsewhere), don't pass that URL straight into image/video/audio/qa — import it first:
files(import_remote, download_url="https://…")→ get back{ url: "https://cdn.framehood.ai/…", content_type, size }.- Use that returned
urlasimage_url/video_url/ etc. in any generation orqacall.
Only https:// sources are accepted, and only image/video/audio files — anything else (a PDF, an SVG, an http:// link) is rejected with a clear error. Files are capped at 200 MB. The URL you passed in is never stored or returned — only the resulting Framehood file.
Organizing an uploaded file into a project
Generated results are added to your library automatically, but a file you brought in yourself (upload / create_upload / import_remote) has no library entry, so it can't be assigned to a project. import_to_library mints one:
files(import_to_library, filename="<the uploaded key>", project_id?="<id>")→ get back{ asset_id, url, type, size, project_id }.- Use
asset_idwithproject(assign)(or passproject_idabove to assign in the same call).
It references your existing file — nothing is copied, and it's free. Only image/video/audio files can be imported. It resolves your OWN private uploads only — not a published (public/) file, and not a teammate's file. Re-importing the same file creates a new library asset each time.
actor
Temporarily disabled
Actors are being reworked — the actor tool is not currently advertised over MCP. The reference below is kept for when it returns.
Create and manage persistent actors. An actor carries visual identity, voice, personality, wardrobe, and motion across image / video / audio generations — pass its actor_id (act_…) to those tools to reuse the same actor. Training a custom actor is a paid feature; the free tier can use built-in actors.
| Action | Key arguments | Does |
|---|---|---|
create | name, images_data_url | train a new actor from a ZIP of reference photos; optional voice, personality, wardrobe, motion, brand |
update | actor_id | change voice, personality, wardrobe, motion, or brand |
list | prefix? | list your actors, optionally filtered by name prefix |
get | actor_id | fetch one actor's full profile |
delete | actor_id | remove an actor |
batch | actor_id, prompts, kind?, duration?, tier? | generate many images or videos for one actor in a single call |
images_data_url is a public ZIP URL with 4–30 reference photos. Attach a voice via voice_id (existing) or voice_sample_url (a clean 30–60s sample). Voice cloning is additionally gated behind its own flag, independent of the actor rework above — it stays disabled (pending pricing) even once actors return; linking an existing voice via voice_id is unaffected.
billing
billing(action) — credits, plan, and upgrades for your organization. Read actions are open to any member; the rest are owner-only.
| Action | Key arguments | Does |
|---|---|---|
balance | — | shared credit balance + your role |
transactions | limit? | recent credit ledger (newest first) |
plan | — | your tier, monthly allowance, manage link (owner) |
plans | — | available packages |
subscribe | step | owner: Stripe Checkout link for a package |
preview | step | owner: prorated cost of switching |
change | step | owner: switch package, prorated, charged now |
cancel | reactivate? | owner: cancel at period end, or resume |
manage | — | owner: Stripe Customer Portal link |
topup | amount_eur | owner: buy a one-off batch of extra credits now (min €20, at the extra-usage rate); returns a hosted invoice link |
extra_usage | — | owner: view the auto-top-up (Extra usage) config |
set_extra_usage | enabled?, trigger_below?, amount_eur?, extra_usage_cap_eur? | owner: configure automatic overflow top-ups |
request_upgrade | note? | member: email the owner to top up |
See Credits & plans.
org
org(action) — your organization. Members share a credit pool and actors.
| Action | Key arguments | Does |
|---|---|---|
info | — | your org + your role |
members | — | list members (role + suspended state) |
spend | — | per-member credit spend |
trend | days? | daily org spend (7–90 days, default 30) |
invite | email | owner: invite by email |
accept_invite | token | join an org with an invite token |
remove | email | owner: remove a member |
set_role | email, role | owner: member↔admin |
suspend / enable | email | owner or admin: block / unblock a member |
A suspended member loses org access and can't draw on the shared credit pool. Plan and payment live in the billing tool; out of credits, members use billing(request_upgrade).
get_status
get_status(action) — poll or manage jobs.
| Action | Key arguments | Does |
|---|---|---|
status | job_id | poll one job → queued · running · succeeded (with outputs) · failed (with error) |
status (batch) | job_ids | check up to 50 jobs in ONE call → {summary, jobs[]} |
list | kind?, status?, since?, limit?, cursor? | the generation-history feed (since = RFC3339 or "1h" / "30m" shorthand) |
cancel | job_id | cancel a non-terminal job — a queued job cancels immediately, no charge. A running job's cancel is a best-effort request: it errors and the job keeps running if the model doesn't support cancellation at all; otherwise the request is accepted but the job may still complete and be charged if the model doesn't honor a mid-run stop — poll status for the outcome |
status is the default action.
Submitting several jobs? Fire all the submits in parallel, collect the returned job ids, then poll ONCE with job_ids=[…] every ~30–60s until summary.running + summary.queued is 0 — don't poll jobs individually. Each batch entry carries status, done, and (when terminal) outputs/credits or error; an id you don't own (or that doesn't exist) comes back as status: "not_found" without failing the batch.
models
models(action) — browse the model catalog. Every tool already picks a sensible default model, so reach for this only when you want a specific one.
| Action | Key arguments | Does |
|---|---|---|
list | tool? | reachable models grouped by tool (optionally filtered to image / video / audio / qa) |
guide | model | one model's parameters + prompting tips |
To use a specific model, pass model (and optional params) to the generation tool — e.g. image(create, model="flux_pro", params={…}).
submit_feedback
submit_feedback — send a note to the Framehood team: a bug, a feature request, or general feedback.
| Argument | Does |
|---|---|
message | the feedback text |
category? | bug · feature · other |
context? | what you were doing / which tool or model |
job_id? | the job this feedback is about (e.g. job_abc123) — links the ticket to that generation |
Workspace tools (also over MCP)
These three are advertised over MCP alongside the generation and account tools and let you organize and authenticate your work.
library
library(action) — search and manage your generated assets:
list(query,type,project,limit,offset) — search past generations by prompt/name, filter by media type or project, paginate.trashed— list the trash.trash(id) /restore(id) — soft-delete or recover an asset. The trash is auto-purged after 10 days. You can trash your own assets; org owners/admins any.
project
project(action) — group generations into projects:
list— your personal projects + the org's shared ones.create(name,visibilitypersonal|shared,description).update(id) — rename, change visibility / description, or owner.delete(id) — owner only; the assets stay in the library.assign(asset_id,id) — put an asset in a project; omitidto unassign.use(id) — set your active/default project; new generations land there automatically. Omitidto clear.current— show your active project.
api_keys
api_keys(action) — programmatic keys for the REST API and CLI:
list— your keys (metadata only).create— mint a key; the secret is returned once.delete(key) — revoke a key.
CLI / REST surface
The CLI and the REST API drive the same tools listed above — the CLI maps its commands onto them, and the REST API exposes the same operations as plain HTTP endpoints. They additionally surface a few things the MCP tools intentionally don't:
- Raw job submission —
POST /v1/jobs/{kind}to run a specific model kind directly. (Over MCP you don't submit to a kind — passmodel+paramstoimage/video/audio/qa, and browse the catalog with themodelstool.) - Uploads —
PUT /uploadandPOST /upload-from-urlto bring your own input files into storage. - Browser billing flows — the Stripe card-entry and customer-portal endpoints (
POST /billing/card,/billing/portal, …), which finish in a browser.
See the REST API reference for the full endpoint list. Over MCP your client handles auth, so you rarely need to touch these directly.