About scenario
Scenario is an MCP server for generating images, video, 3D assets, and audio across more than 500 models, with custom training. It supports discovery, generation, asset handling, and project-scoped workflows.
Key features
- 01Generate images, video, 3D, and audio
- 02Discover and inspect model schemas
- 03Run and monitor generation jobs
- 04Upload, preview, and download assets
- 05Organize assets into collections and add tags
- 06Plan multi-step generation workflows
Use cases
- 01Generate creative assets from prompts or uploaded inputs
- 02Create 3D assets from concept images
- 03Review and download generated project assets
MCP support
Scenario exposes generation, model discovery, job, and asset tools to MCP clients. Catalog tools also cover collections, tagging, analysis, training, and other project operations.
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ scenario-labs/ skills --skill scenarioAfter installing, type/scenarioin Claude Code to confirm it loaded.
Bundled filesreferences/setup.mdreferences/shared-assets.md
Details
From the project READMEScenario
Overview
Scenario generates images, video, 3D, and audio across 500+ models, plus custom training.
Setup
- Endpoint:
https://mcp.scenario.com/mcp(Streamable HTTP). Prefer OAuth: no credentials pass through the conversation. - Client config, headless API-key setup, and per-client re-authentication: references/setup.md.
- Never ask an agent to collect, encode, or echo a secret.
- A previously authenticated connection now failing: re-authenticate before debugging.
diagnostics_runnames the layer (auth,tenant-scope,api-unreachable, orhealthy).
Two tool kinds:
- Direct: the loop below, plus
asset_get,job_get,jobs_list, andmodels_list.?toolsets=fullexposes everything. - Catalog: the rest (collections, tagging, analysis, training, members, keys).
scenario_tools_searchtakes onlyquery(tool name or keywords) andlimit; returns schema and lane.scenario_tool_execute_read/write/deleteruns the tool with{name, parameters}.- Every input goes inside
parameters, scope ids anddry_runincluded. Nearly every catalog tool declares scope ids;plan_generationtakes onlydescription. - Direct tools take scope, and
model_runitsdry_run, at top level. - The executor drops a misplaced key silently:
argumentssurfaces as a false scope error, and a top-leveldry_runruns the job, billed. - The lane is the result's own
permission, not the verb:asset_downloadandasset_analyzeare write-class.
Scope first
- Resolve scope first, then pass
team_idandproject_idon every call. teams_listreturns the teams with their projects.projects_listneeds ateam_id, so it cannot come first.- Confirm the pair with the user: a guess writes into someone else's project. Unattended, take the pair from the task; if it names none, stop and list the choices.
- The server fills scope only for read-only tools with one candidate. Otherwise it fails, naming the wrong half.
- Scope omissions commonly fail when a second team or project becomes available.
Quick reference
| Step | Tool | Notes |
|---|---|---|
| Resolve scope | teams_list, then projects_list | Once per session; pass the ids on every call |
| Find a model | search or recommend | Free; recommend for a capability, search for a name |
| Get the schema | model_schema_get | Always before model_run; check runs_as and caps |
| Generate | model_run | Schema-conformant parameters; dry_run for cost |
| Wait | jobs_wait | Whenever model_run returns a job_id without assets; never loop job_get |
| View / save | asset_display / asset_download | Never paste raw asset URLs; format converts images, meshes |
| Inspect an asset | asset_get | Free; dimensions, duration, firstFrame / lastFrame ids |
| Upload inputs | upload_asset + upload_asset_complete | Local files become asset_ids |
| Refine a prompt | prompt_spark | Advisory rewrite; needs model_id |
| Quota / debugging | usage, diagnostics_run | CU consumption; diagnose MCP prompt |
| Saved preferences | memory_recall | OAuth only; before generating in a project, again after switching |
- Multi-step request ("product video with voiceover", "concept to 3D"): catalog
plan_generation(read lane). Plain words indescription, ordered steps out, each naming a tool and an optional model hint. It runs nothing. A single step goes torecommend. - Stated preference ("always 9:16"): catalog
memory_set(delete lane),scopeproject_user_memory(user_memoryacross projects). It replaces the whole layer:memory_getand merge first.
Worked example
Generating a stylized game prop image:
- Discover the model:
recommendwith the user's own words aspromptfor a capability.searchwithtarget="models",query="<model name>",public=truefor a known name. It ranks by keyword and itsfiltershold no capability key, so a capability-worded query can rank the wrong output type first.- The user's own trained models: omit
publiconsearch(it has no private flag); passinclude_private_models: trueonrecommend. - Re-discover ids each time: availability differs per team.
model_schema_geton the pick: exact field names, types, required flags, defaults, and caps.- Overrunning a cap such as the prompt's
max_lengthis a 400, never a trim. - File fields take asset ids, even when named
...Url. cost_impact: trueflags the fields that move the price.
- Overrunning a cap such as the prompt's
- If the schema carries
runs_as("lora"or"composition"), never send that model's id tomodel_run. Itsrun_with.required_argumentsholds the real call:model_idthere is the base model, and itsparameters(thelorasormodelIdwiring) merge with your inputs from the same schema.required_argumentsalone discards your prompt. - Optional:
prompt_sparkrewrites a thin prompt into an on-model one. Pass the discovered id (a LoRA's own, not its base) and the draftprompt. Skip deliberate prompts. model_runwithmodel_idand schema-conformantparameters.- Unless the user says cost does not matter, price first with
dry_run: true, top level besidemodel_id. No job is created, andcreativeUnitsCostis this exact payload's price. - A
recommendquote assumes defaults;cost_impactfields move the real number. - Run: inline assets (
status: success) need no wait; otherwise passjob_idtojobs_wait. - Unfinished status:
in_progressafter a timed-out wait; withwait=false,queued,in-progress, orwarming-up. All needjobs_wait.
- Unless the user says cost does not matter, price first with
jobs_waitwithjob_ids=[...](up to 32).- Each completed row carries
assetIdsandcuCost: nojob_getfollow-up. - On timeout (
in_progress), re-call withpending_job_idsasjob_ids; never re-run or cancel. No timeout argument exists. - Failed jobs are reimbursed, except xAI generations stopped by moderation.
- Each completed row carries
asset_displayshows the asset inline. Itsformatpicks the rendering:display(default): inline image, links, viewer data; does not disable the host widget.viewer: lighter payload for hosts rendering the interactive widget.json,markdown: metadata and links.
asset_downloadreturns a file URL, one call per asset; save it withcurl -L(it may redirect).formatconverts images (png,webp,jpg;gifkeeps an animated GIF animated, while thepngdefault flattens it to one frame) and meshes (glb,fbx,obj). Omit it for video and audio, or it fails with 400Invalid target format.curlfailing withhost_not_allowedorCONNECT tunnel failed, response 403while MCP calls work points at the host's egress policy. A CDN 403 alone does not: fetch a fresh URL first.- Sandbox network access names the setting. On some hosts it is an organization allowlist the user may not control.
- Meanwhile hand over
asset_display'sapp_url. The user downloads there and, for a mesh, picks an export formatasset_downloaddoes not offer. - The signed URL expires: a last resort, never the deliverable.
Local inputs go up with upload_asset:
- Always
file_name,content_type, andkind(image,audio,video,3d), plus exactly one offile_sizeordata. Without either it fails withEither 'file_size' … or 'data' is required. - Prefer
file_size, the exact byte count read from disk, and omitdata. The reply carries presigned part URLs andinstructions. - PUT each part's raw bytes to its URL with no added headers: a checksum header makes the store
answer 403. Check every PUT returned 200, then
upload_asset_completewith theupload_id. - Inline base64
dataonly under 100KB, and only when no shell can PUT parts. It returns the asset directly, with no complete step; a larger file is rejected, naming the cap. - Both calls take scope and nothing else: no parts list, no etags.
- Inline
datais decoded insideupload_asset: a decode error there (libpng read error,Corrupt JPEG data,unable to read pixels) means the base64 string broke, not the file.- The agent retypes every character of
data; a 57K-character string broke that way. - Re-sending it repeats the error. With a shell, switch to the
file_sizepath, even when the user asked for inline, and say why.
- The agent retypes every character of
- On the
file_sizepath the server decodes at completion:Upload upl_… failed:withCorrupt JPEG data,premature end of data segment,bad Huffman code, orlibpng read errormeans it could not.- Check for a corrupt source, a truncated or re-encoded body, a missing part, or a wrong
file_size. - Confirm the local file opens under its declared content type, then start over with a fresh
upload_assetand raw PUTs. Never re-complete the sameupload_id.
- Check for a corrupt source, a truncated or re-encoded body, a missing part, or a wrong
- A phone's HEIC photo uploads as is (
content_type: "image/heic"): do not convert it.Unhandled image formatlists whatkind: "image"accepts; convert any other type locally. - A host with no shell cannot PUT parts. The user uploads at app.scenario.com and the agent continues from the asset id.
Filing is part of delivering, not a tidy-up:
collection_createtakes a name and the scope pair only.asset_idssent there is silently ignored, so adding is always a second call:collection_add_assetswithcollection_idandasset_ids.- It accepts any number of ids, batches by 49, and skips duplicates. Results separate
already_present_asset_idsfromadded_asset_ids. - After failure, safely resend the whole list: the error counts additions; retries add the rest.
- Confirm from that reply:
searchfilters={"collection_ids": [...]}lags on a fresh write. asset_add_tagsis additive, oneasset_idper call. A tag the asset already carries returns 400Duplicated tag: readtagsviaassets_get_bulkfirst and send only the missing ones.
Reusable templates and reference content follow the shared asset lifecycle: resolve existing assets, stage new versions, publish only through a supported operation, and verify public access before recording public ids. Uploading and filing alone are not publication.
Errors and recovery
context_missing: nothing resolved.teams_list,projects_listif necessary.context_ambiguous: several fit. Present the options; the user picks (unattended: the task names the pair, else stop and list).- 403 Forbidden: usually wrong scope, not missing; re-check the id pair. A private asset input
reads only from its own project:
- ask which project holds it (unattended: the task names it, else stop);
- run catalog
asset_copy(write lane) with that project asproject_id, plusasset_idandtarget_project_id; - use the returned id.
asset_copycopies only between projects of one team.
- 403 naming a plan: surface the upgrade or switch models; a retry never clears it.
recommendpre-flags these asrequires_plan_upgrade(never run one) unless its response says plan gating is_degraded; then this entry is the backstop. - 429 with
details.actionName=parallel-custom-jobs: the per-team generation concurrency ceiling. Keep at mostactionLimitjobs in flight: launch withwait=false, and retire jobs withjobs_waitbefore launching more. An immediate retry repeats the error. - 429
custom-generation, or naming a quota, balance, or seat limit:actionLimitis the period's CU allowance (Free: 50 a day),actionValuewhat is already consumed. A run priced above the gap is refused, so a lowactionValuemeans this payload costs too much, not that credits ran out.- Stop the batch,
dry_runthe payload, offer a cheaper member or fewer outputs, or report the reset or upgrade (unattended: report and stop). usagetakes the sameproject_id(orscope: "team").- A per-user cap goes to the team admin; a seat-cap error can also block uploads. Never change billing or membership yourself.
- 429
Too Many Requests. Please, wait N seconds(remainingSeconds): Relax mode: the plan's CU are spent, so every job brings a wait.- The wait scales with the payload's cost: a heavy job (high effort tier, long video) can stay
locked while a cheaper one runs. Never re-submit the same payload inside
remainingSeconds. - Any team member's job restarts the team's wait, and waits lengthen as the team keeps generating, so a batch slows down. A refused attempt is free.
- Detect Relax mode from this refusal:
usagereports consumption, not remaining CU. If no tool documents how to check the wait, say it is unknown. - Stop the batch. Offer a CU top-up (it ends Relax mode at once) or an upgrade; else a cheaper
payload (
dry_runit first), fewer outputs, or a later run (unattended: report and stop, never relaunch). - Rules and plan caps: Relax Mode.
- The wait scales with the payload's cost: a heavy job (high effort tier, long video) can stay
locked while a cheaper one runs. Never re-submit the same payload inside
- Other 429, including an unfamiliar
actionName: never infer its behavior from the name. Report it and rundiagnostics_runin the same scope before choosing a recovery. - Transport error on
model_run(no HTTP status): the request may still have landed. Runjobs_listbefore any re-run, or a lost response becomes a double charge. Adry_runcreates no job and always retries safely. - 400
Cannot cancel this type of job: a launched job is committed spend.job_cancelrejects most generation jobs, so plan batches with no abort path. - 404 on
asset_get: the id does not exist, and no retry makes it appear. Re-readassetIdsoff thejobs_waitorjob_getrow: a guessed or mistyped id is the usual cause. - 400
At least one of query, filter, image, or images must be provided:searchnever lists bare. A newest-first asset listing istarget="assets",filter="createdAt EXISTS",sort_by=["createdAt:desc"]. recommendclient-side timeout: it ranks on live data and commonly runs 30 seconds, sometimes over a minute. Wait or re-call it; never fall back tosearchfor a capability.
Common mistakes
- Pricing a later step with a placeholder id (
asset_placeholder): file ids resolve before pricing, so thedry_runreturns 404 and quotes nothing. Quote it fromrecommend, thendry_runonce the real asset exists. - A bare value where the schema says
array: true: silently dropped, so the run ignores your reference or LoRA.asset_geton the output echoes what the run consumed (metadata.referenceImages,parentId): the cheapest proof. - Taking
recommend'sranked[0]blindly. Readnext_step.typefirst:no_match: stop generation, even with ranked partial options. Explainunmet_requirementsandsuggested_action; never silently relax requirements. Schema checks or a successful dry run do not overrideno_match.ask_user: present the options; the user's pick wins (unattended: the task, elseproceed).proceed: preferspecialty.model_id, else the firstrankedentry not marked deprecated in itscaveats,tradeoff, orexplanation. The ranking measures performance, not lifecycle.
- Debugging blind: the
diagnoseMCP prompt returns trace ids, as doesdiagnostics_runwith the failing call's scope pair and anymcpt_ids seen asobserved_trace_ids. - Guessing a file field's name: 400
Input image is required(orreferenceImages,startImage,frontImage,video: each model names its own) means the payload lacks the schema'srequiredfield. Copy its name verbatim. A URL there reads as missing: file fields take asset ids only. - Feeding an image field a video, audio, or 3D asset: 400
Unhandled image formatlists the accepted types.asset_getreports the asset'smimeType; pull a still with the clip'sfirstFramewhen the model wants an image. filters.kindonsearchwithtarget="models": a 400"kind" is not a filterable field, sincekinddescribes assets. Narrow models byfilters.type(architecture) ortags, orrecommenda capability.- Filtering
models_listbytypewithoutprivacy: "public": a 400 says so. A private listing narrows bystatusormodality, or goes throughsearch(private by default).