scenario
Use when connecting an AI agent to Scenario (scenario.com) through MCP, or when a task involves generating images, video, 3D, audio, sprites, textures, or game assets. Also when picking a Scenario model, running a LoRA, refining a generation prompt, uploading reference images, waiting on generation jobs, checking credits or quota, hitting Scenario auth, scope, or Forbidden errors, or setting up mcp.scenario.com in Claude Code, Cursor, VSCode, or another agent.
SKILL.md
Scenario
Overview
Scenario generates images, video, 3D, and audio across 500+ models, plus custom training, all through the core loop below.
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 connection that authenticated once and now fails: re-authenticate its client before debugging
anything else.
diagnostics_runnames the failing layer (auth,tenant-scope,api-unreachable, orhealthy).
Tools come in two 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(a tool name or keywords) andlimit. It returns the schema and the 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 left. Anything else fails, and the error names the wrong half.
- Scope errors are the most common failure. They surface on the first call that drops the pair once a second team or project is in play.
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. - Then run. Assets come back inline (
status: success, nothing to wait for), or ajob_idforjobs_wait. - A job's
statusisin_progressafter a timed-out wait, or withwait=falsethe backend's word at creation (queued,in-progress,warming-up). All mean the same state.
- 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. - A timeout (
in_progress) is not an error: re-call with the returnedpending_job_idsasjob_ids, never a secondmodel_runor a cancel. It takes no timeout argument. - Failed jobs are reimbursed, except xAI generations stopped by moderation.
- Each completed row carries
asset_displayshows the asset inline. Itsformatpicks the rendering:display(default): an inline image, links, and viewer data. It does not disable a host's widget.viewer: a lighter payload for a host that renders 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. 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.
- The server decodes the file at completion.
Upload upl_… failed: Corrupt 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 takes any number of ids (it batches them by 49 itself) and skips an id already filed, listing
it under
already_present_asset_idsbesideadded_asset_ids. - So re-sending the whole list after a failed batch is safe: the error says how many went in, and the retry adds 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 each job is followed by a wait.- Never re-submit inside
remainingSeconds. - Stop the batch and invite the user to buy CU (a top-up ends Relax mode at once) or upgrade; else offer a cheaper member, fewer outputs, or a later run (unattended: report and stop, never relaunch).
- Never re-submit 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: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.usageanswers credit questions. - 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).