Scenario
SkillMediaUse 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.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the Scenario skill
What this skill tells your AI
The instructions your AI receives, as published by scenario-labs/skills in skills/scenario/SKILL.md and read by ahel’s review.
Overview
Scenario (scenario.com) generates AI 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, API-key setup for headless use, 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 fails later is re-authenticated per client before anything else is debugged: a server-side authentication fix lands only through a fresh handshake, and diagnostics_run names the failing layer (auth, tenant-scope, api-unreachable, or healthy) with trace ids for support.
The default toolset is wider than the core loop below: asset_get, job_get, jobs_list and models_list are in it too and are called directly, so treat the table as the loop rather than the whole list. ?toolsets=full exposes everything. The catalog tools are the ones outside it (collections, tagging, analysis, training, members, keys): scenario_tools_search with the tool name or plain keywords as query (it takes only query and limit) returns the schema and lane, and the matching scenario_tool_execute_read / write / delete runs it with {name, parameters}, scope ids inside parameters when the target's inputSchema declares them, which nearly every catalog tool does (plan_generation, which takes only description, is the exception), unlike a direct tool's top-level team_id/project_id. The lane is the result's own permission, not what the verb sounds like: asset_download and asset_analyze are both write-class.
Scope first
Resolve scope first, then pass team_id and project_id on every later call. teams_list returns the teams with their projects; projects_list requires a team_id, so it cannot come first. Confirm the pair with the user: a guess writes into someone else's project. A non-interactive run takes the pair from its task instructions; when they name none, stop and list the choices.
The server fills scope in only for read-only tools with one candidate remaining; anything else fails rather than guesses, and the error names which half is wrong (see Errors and recovery). Scope errors are the most common failure here, surfacing mid-session 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 only |
| 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 |
A multi-step request ("product video with voiceover", "concept to 3D") goes to plan_generation (catalog-only, read lane): plain words in description, ordered steps out, each naming a tool and optional model hint; it runs nothing. Single-step: recommend.
Worked example
Generating a stylized game prop image:
recommendwith the user's own words aspromptwhen the need is a capability;searchwithtarget="models",query="flux",public=truewhen you have a name.searchranks by keyword and itsfiltershold no capability key, so a capability-worded query can rank the wrong output type first. For the user's own trained models, omitpubliconsearch(searchhas no private flag); onrecommendthe flag isinclude_private_models: true. Re-discover ids each time: availability differs per team.model_schema_geton the pick: exact field names, types, required flags, defaults, and caps such as the prompt'smax_length(an overrun is a 400, never a trim). File fields take asset ids even when named...Url, andcost_impact: trueflags what moves the price.- If the schema carries
runs_as("lora"or"composition"), never send that model's own id tomodel_run. Itsrun_with.required_argumentsholds the real call:model_idthere is the base model, and itsparameters(thelorasormodelIdwiring) merge into inputs from the same schema. Sendingrequired_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. If cost matters (the default assumption unless the user says otherwise), price first withdry_run: true, a top-level argument besidemodel_id: no job is created and the response'screativeUnitsCostis the exact payload's price; arecommendcost quote assumes defaults, and the schema'scost_impactfields move the real number. Then run: asset_ids come back, or ajob_idforjobs_wait. Thestatusbeside it isin_progresswhen the server's wait budget ran out; withwait=falseit is the backend's live word at creation (queued,in-progress,warming-up), a spelling that is not a different state.jobs_waitwithjob_ids=[...](up to 32); each completed row carriesassetIdsandcuCost, so nojob_getfollow-up; on timeout re-call with the returnedpending_job_idsasjob_ids. Failed jobs are reimbursed, except xAI generations stopped by moderation.asset_displayshows the asset inline; itsformatpicks the rendering:display(the default) returns an inline image plus links and viewer data;viewerreturns a lighter payload for a host that renders the interactive widget;jsonandmarkdownreturn metadata and links.displaydoes not disable a host's widget. For PNG files, useasset_downloadwithformat: "png", one call per asset.asset_downloadreturns a file URL (save withcurl -L, it may redirect).formatis an image conversion (png,webp,jpg, andgifto keep an animated GIF animated: thepngdefault flattens it to one frame) and nothing else; omit it for video, 3D, and audio. Whencurlreportshost_not_allowedorCONNECT tunnel failed, response 403while MCP calls work, check the host's proxy or sandbox egress policy. A CDN 403 alone does not establish the cause; request a fresh download URL before diagnosing it: Sandbox network access names the setting that lifts it, an organization-level allowlist on some hosts that the user may not be able to change. Meanwhile hand over theapp_urlthatasset_displayreturns, where the user downloads directly and, for a mesh, picks the 3D export format the app offers, none of whichasset_downloadconverts to. The signed URL also opens in a browser but expires, so it is a last resort, never the deliverable.
Local inputs go up with upload_asset: always file_name, content_type, and kind (image, audio, video, 3d), plus exactly one of file_size or data, since the call fails without either. Prefer file_size, the file's exact byte count read from disk, and omit data: the reply carries presigned part URLs and instructions; 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_complete with the upload_id. Inline base64 data only under 100KB (that path returns the asset directly, with no complete step); a larger file is rejected naming the cap. Scope rides on both; they take no other fields: no parts list, no etags. The server decodes the file at completion, so Upload upl_… failed: Corrupt JPEG data, premature end of data segment, bad Huffman code or libpng read error means the uploaded image could not be decoded: check for a corrupt source, a truncated or re-encoded body, a missing part, or a mismatched file_size. Confirm the local file opens, its content type matches, and its byte count is correct, then start over with a fresh upload_asset and raw PUTs; never re-complete the same upload_id. Unhandled image format at upload lists what kind: "image" accepts: convert locally first. A host with no shell cannot PUT parts at all; the user uploads in the web app at app.scenario.com and the agent continues from the asset id.
Filing is part of delivering, not a tidy-up: run the catalog tools above with arguments under parameters (never arguments, which the executor drops silently, surfacing as a scope error that is not one). collection_create takes a name and the scope pair only; asset_ids sent there is ignored without an error, so adding is always a second call, collection_add_assets with collection_id and asset_ids, and re-adding a filed asset is a hard 400 naming the duplicate: drop it and continue. Confirm membership with assets_get_bulk and read each record's collectionIds; search filters={"collection_ids": [...]} lags on a fresh write. collection_add_assets takes at most 49 ids per call (past it, 400 You can not add more than 49 assets at once), so chunk a larger set. asset_add_tags is additive, one asset_id per call, so a set is one call per asset, and a tag the asset already carries returns 400 Duplicated tag: read tags off asset_get first and send only the missing ones; skip the write when none are missing.
For reusable templates or 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
| Error | Recovery |
|---|---|
context_missing | Nothing resolved: teams_list, then projects_list |
context_ambiguous | Several fit: present the options; the user picks (non-interactive: task instructions name the pair, else stop and list) |
| 403 Forbidden | Usually wrong scope, not missing: re-check the id pair |
| 403 naming a plan | Surface the upgrade or switch models; retrying never clears it. recommend pre-flags these as requires_plan_upgrade (never run one) unless its response says plan gating is _degraded; then this row is the backstop |
429 with details.actionName = parallel-custom-jobs | Per-team generation concurrency ceiling: keep at most actionLimit jobs in flight, use wait=false for launches and jobs_wait to retire existing jobs before launching more. An immediate retry repeats the error |
| 429 naming a quota, balance, or seat limit | Read the reason and details together, including actionLimit and limitScope when present. Generic plan-limit wording alone does not distinguish concurrency from consumption or feature access. A CU limit does not prove the balance is zero; the requested run may exceed what remains. Stop the affected batch, use usage for consumption, and report the stated remedy. A per-user cap goes to the team admin; a seat-cap error can also block uploads. Do not change billing or membership automatically |
| 429 with an explicit retry delay | Respect the returned delay before retrying; this is not evidence that the user needs an upgrade |
Other 429, including an unfamiliar actionName | Do not classify every other action as a count quota, or infer a training quota's behavior from its name. Report the error and run diagnostics_run in the same scope before choosing recovery |
jobs_wait timeout (in_progress) | Not an error: re-call with the returned pending_job_ids, never a second model_run or a cancel; it takes no timeout argument |
Transport error on model_run (no HTTP status) | The request may still have landed: jobs_list before any re-run, or a lost response becomes a double charge; a dry_run call creates no job and always retries safely |
400 Cannot cancel this type of job | A launched job is committed spend: job_cancel rejects most generation jobs, so plan batches with no abort path |
400 Invalid target format | format converts images only: omit it for video, 3D, audio |
Either 'file_size' … or 'data' is required on upload_asset | Read the byte count from disk and send it as file_size (omit data); inline data is for files under 100KB only |
Upload upl_… failed: Corrupt JPEG data (or libpng read error) | The uploaded image could not be decoded: check the source file, content type, byte count, and part transfers before a fresh upload; re-completing does not repair corrupt bytes |
404 on asset_get | The id does not exist and no retry makes it appear: re-read assetIds off the jobs_wait or job_get row, since a guessed or mistyped id is the usual cause |
400 At least one of query, filter, image, or images must be provided | search never lists bare: a "newest first" asset listing is target="assets", filter="createdAt EXISTS" with sort_by=["createdAt:desc"] |
recommend client-side timeout | It ranks on live data and commonly runs 30 seconds, sometimes over a minute: wait or re-call it; do not fall back to search for a capability |
Common mistakes
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 681
- Forks
- 82
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
scenario-scenario-labs- Source
- github.com/scenario-labs/skills