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, wa
Install
npx skills add https://github.com/scenario-labs/skills/tree/main/skills/scenario
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install scenario-labs-skills@llmmart
git clone https://github.com/scenario-labs/skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole scenario-labs/skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
Scenario
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 (setup reference) before anything else is debugged; diagnostics_run names the failing layer (auth, tenant-scope, api-unreachable, or healthy).
The default toolset is wider than the loop below: asset_get, job_get, jobs_list and models_list are called directly too. ?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}. Every input goes inside parameters, dry_run and scope ids included (nearly every catalog tool declares scope ids; plan_generation takes only description), though direct tools take scope, and model_run its dry_run, at top level. The executor drops a misplaced key without an error: arguments surfaces as a false scope error, and a top-level dry_run runs the job, billed. 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, 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 |
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. A stated preference ("always 9:16") goes to catalog memory_set with scope project_user_memory (user_memory across projects): it replaces the whole layer, so memory_get and merge first, and it runs on the delete lane.
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. Itsstatusisin_progressafter a timed-out wait, or withwait=falsethe backend's word at creation (queued,in-progress,warming-up); none is 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).formatconverts images (png,webp,jpg, andgifto keep an animated GIF animated: thepngdefault flattens it to one frame) and meshes (glb,fbx,obj); omit it for video 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 expires: 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. A phone's HEIC photo uploads as is (content_type: "image/heic"), so do not convert it first; Unhandled image format lists what kind: "image" accepts, so convert a file of any other type locally. 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. 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. It takes any number of ids (it batches them by 49 itself) and skips an id already filed, listing it under already_present_asset_ids beside added_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, since search filters={"collection_ids": [...]} lags on a fresh write. asset_add_tags is additive, one asset_id per call, and a tag the asset already carries returns 400 Duplicated tag: read tags via assets_get_bulk first and send only the missing ones.
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 custom-generation, or naming a quota, balance, or seat limit |
actionLimit is the period's CU allowance (Free: 50 a day) and actionValue what is already consumed; a run priced above the gap is refused, so a low actionValue means this payload costs too much, not that credits ran out. Stop the batch, dry_run the payload, offer a cheaper member or fewer outputs, or report the reset or upgrade (unattended: report and stop). usage takes the same project_id (or scope: "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 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 |
Never infer its behavior from the name: report it 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 and glb/fbx/obj meshes: omit it for video, 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
- 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, the run ignoring your reference or LoRA.asset_geton the output echoes what the run consumed (metadata.referenceImages,parentId), the cheapest proof it was not. - Taking
recommend'sranked[0]blindly: readnext_step.typefirst. Onask_user, present the options; the user's pick wins (non-interactive: task instructions, elseproceed). Onproceed, preferspecialty.model_id, else the firstrankedentry its own text does not mark deprecated, readingtradeoffandexplanationas well ascaveats, since the flag lands in any of them: the ranking is by measured performance, not by lifecycle, so a deprecated member can top it. - Debugging blind: the
diagnoseMCP prompt (ordiagnostics_runwith the scope pair the failure happened under and anymcpt_ids seen asobserved_trace_ids) returns trace ids;usageanswers credit questions. - Guessing a file field's name: 400
Input image is required(orimages,referenceImages,startImage,frontImage,video,model, each a different model's name for the same idea) means the payload never carried the field the schema'srequiredlist names, so copy that name verbatim; a URL in a file field reads as missing too, since file fields take asset ids only. - Feeding an image field a video, audio, or 3D asset: 400
Unhandled image formatlists the image types it accepts.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 withfilters.type(the architecture) ortags, or go throughrecommendfor a capability.- Filtering
models_listbytypewithoutprivacy: "public": a 400 says so. A private listing narrows bystatusormodality, or goes throughsearch(private by default).
Files (skills)
-
references
-
setup.md 5.7 KB
# Connection setup Endpoint: `https://mcp.scenario.com/mcp` (Streamable HTTP). Every fact here is also on the public [connection guide](https://mcp.scenario.com/docs) and [troubleshooting page](https://mcp.scenario.com/docs/troubleshooting); when they disagree with this file, they win. ## Transport: native HTTP first Clients with native Streamable HTTP support connect to the URL directly; that is the reliable path. `mcp-remote` (`npx mcp-remote@latest https://mcp.scenario.com/mcp`) is a stdio bridge for clients without native HTTP, it needs Node.js installed, and it is the usual source of repeated sign-in prompts and tokens that never persist. A client that offers both takes the native URL configuration. ## OAuth (preferred) Add the endpoint and sign in with a Scenario account when the client prompts; no credentials pass through the conversation. Claude apps (claude.ai, Claude Desktop, and Claude Code signed in with the same Claude account): one custom connector serves all three. Free, Pro or Max: on claude.ai, Customize, Connectors, Add, Add custom connector, the URL, then OAuth in the browser (Free allows one). Team or Enterprise: an owner, or on Enterprise a member whose custom role includes Manage access to Libraries, adds it once under Organization settings, Connectors, Add, Custom, Web (other members see "Ask an organization owner"); each member then clicks Connect under Customize, Connectors and signs in with their own Scenario account. Keep it on OAuth: a fixed API-key header on an organization connector gives every member that key's access. Claude Code without that connector (not signed in with that account, or nobody has added it yet; OAuth starts on first use, `/mcp` authenticates by hand): ```bash claude mcp add --transport http scenario https://mcp.scenario.com/mcp ``` Claude Desktop without Connectors: the `mcp-remote` stdio bridge in `claude_desktop_config.json`, then restart the app. A url-only entry there is rejected at startup ("not valid MCP server configurations and were skipped"), since that file loads stdio servers only: ```json { "mcpServers": { "scenario": { "command": "npx", "args": ["-y", "mcp-remote", "https://mcp.scenario.com/mcp"] } } } ``` Cursor (Settings, MCP; native OAuth and HTTP from 1.0), Windsurf (Cascade, Configure MCP): ```json { "mcpServers": { "scenario": { "url": "https://mcp.scenario.com/mcp" } } } ``` Codex (`codex mcp login scenario` starts the OAuth sign-in): ```bash codex mcp add scenario --url https://mcp.scenario.com/mcp ``` VS Code: Command Palette, `MCP: Add Server`, HTTP, the URL. Zed: `{ "context_servers": { "scenario": { "url": "https://mcp.scenario.com/mcp" } } }` in settings. Gemini CLI: the server in `~/.gemini/settings.json`, then `/mcp auth scenario`. Other clients: the standard URL through their CLI or configuration file. Verify with a free call: "List my Scenario teams" should return `teams_list` output. `diagnostics_run` names the failing layer when it does not. ## Re-authenticating A connection that worked and then fails, or a server-side authentication fix, needs a fresh handshake: clear the client's stored authorization and connect again before debugging anything else. - Claude Code: `/mcp`, select Scenario, Clear authentication. - Cursor: Settings, Tools & MCP, deactivate and reactivate the server. - VS Code: Command Palette, `MCP: List Servers`, remove and re-add. - claude.ai and Claude Desktop: Customize, Connectors, remove and re-add the custom connector (Team or Enterprise: reconnect instead). For Claude Desktop's `mcp-remote` config, Settings, Developer, Edit Config, remove the entry and `rm -rf ~/.mcp-auth`, then re-add and restart. - Codex: `codex mcp logout scenario`, then `codex mcp login scenario`. - OpenCode: `opencode mcp logout scenario`, then `opencode mcp auth scenario`. Claude Desktop reopening the browser on every message is orphaned `mcp-remote` processes: quit the app fully and reboot; if it persists, `pkill -f mcp-remote && rm -rf ~/.mcp-auth`; then switch to the Connectors setup above, which ends the problem for good. ## API keys (headless or CI) Where nobody is present to complete the OAuth prompt, the same endpoint also authenticates with a Scenario API key sent as an HTTP header, and a client whose config file passes no headers (Claude Desktop's `claude_desktop_config.json`) reaches it through `mcp-remote`. Keys are created in the Scenario portal (the `api_key_create` catalog tool, read lane, returns that link and nothing else, so calling it puts no secret in the conversation). Wiring one into a client is an operator task done by hand: the [connection guide](https://mcp.scenario.com/docs#using-an-api-key) carries the header format and the current steps for each client. Credentials stay out of the conversation. An agent asked to set this up points to the guide and stops there. It does not ask for a key or secret, does not place one in a command, a config file, or a message, and does not repeat one that appears in its context. ## Server-side applications OAuth from a web or server application fails with `redirect_uri must use a loopback address or custom URI scheme` until the application's origin is on the server's allowlist. Dynamic client re-registration does not help: only the origin is gated. An API key serves the application without OAuth for generation and project work; team-level administration (model access lists, member roles and caps) needs per-user OAuth with a team admin, since the server refuses API keys there. To get an origin allowlisted, contact [support](https://www.scenario.com/contact-us?intent=scenario-mcp-issue) with the bare `https` origin (the port if any, no path), whether it is needed on production or a preview environment, and the tools the application will call. -
shared-assets.md 6.2 KB
# Shared asset lifecycle Use this process for reusable templates and reference content. Uploading, tagging, and adding to a collection make an asset available in its scope; none makes it public. Publication status and creative validation are separate: an accessible template may still fail an alignment or visual-quality test. ## Reuse before creating another copy Read the owning skill's JSON manifest. Select the template version and required file role, preserving the user's dimensions and purpose. A file entry's `asset_id`, when present, is a verified public reference; confirm it is accessible before use. Otherwise discover catalog `assets_list`, then call `scenario_tool_execute_read` with `name: "assets_list"` and `parameters: {team_id, project_id, limit: 100}` in the user's chosen scope. Follow `nextPaginationToken` via `page_token` until a match or exhaustion. Match the returned tags locally against all three values: manifest `tag`, template `name`, and file role. Do not pass `filters.tags` on this private listing: the API rejects that combination. If absent, `search` with `target: "assets"`, `public: true`, `query` equal to the template name, and the chosen `team_id`/`project_id`, then check the same tags on returned assets. Search can miss an existing upload, so do not use an empty search alone to decide that an upload failed. Use `asset_get` for type and dimensions. For known uploads, reuse the local source and previous content checks; publication does not require downloading or comparing the files again. Inspect unfamiliar content with `asset_display`; for numerical masks, inspect pixels from a verified local copy or download when none is available. A caption or thumbnail cannot verify alpha or mask polarity. On retrieval failure, check scope and access before treating a recorded asset as absent; preserve the receipt. If nothing accessible matches, use the owning skill's local builder and MCP upload flow. Reuse matching assets without uploading again. Source hashes identify the built file bytes, not a guarantee that a downloaded platform copy uses identical encoding. The same reuse rule applies to inputs you just prepared and uploaded. An older returned asset ID or changed encoded file size alone establishes neither deduplication, conversion, nor corruption, and does not justify downloading your known input again. Inspect generated outputs and investigate an input only when there is evidence of a problem. ## Create and stage a version 1. Resolve the destination supplied by the user with `teams_list` and `projects_list`. Reuse a pair already chosen in this session. Never bake destination identifiers into a published skill or manifest. 2. Build or generate the content; inspect it and run its task-specific checks. For templates, record geometry, roles, version, filenames, and source hashes in a separate JSON manifest. A changed geometry or reference contract gets a new version; preserve earlier assets while skills reference them. 3. Upload local files with `upload_asset`: `file_name`, `content_type`, `kind`, and `file_size`, plus scope. PUT the bytes to the returned URLs verbatim, then `upload_asset_complete` with `upload_id` and scope. An `upload_id` in a receipt does not prove every part was transferred: complete only after every PUT succeeded, including when resuming. Assets already returned by generation need no upload. 4. Discover the catalog tools before executing them with `{name, parameters}`. Set descriptive names with `asset_update` (`asset_id`, `metadata: {name, description}`, scope); add the skill tag, template name, and role with `asset_add_tags` (`asset_id`, `tags: [...]`, scope). Reuse a matching collection from `collections_list`, following its pagination, or create one with `collection_create` (`name` and scope only); add missing members separately with `collection_add_assets` (`collection_id`, `asset_ids`, scope). 5. Read `assets_get_bulk` and verify tags, dimensions, and `collectionIds`. Keep a local publication receipt with the actual asset IDs, destination, collection, validation outcome, and next action. Save it before attempting publication so a retry resumes from existing assets. Do not copy private records or signed URLs into the public repository. ## Publish, Scenarians only for the shared skill-reference catalog Publishing shared skill references requires staff access and an explicit publication request. Reuse authorization already given; do not ask again. Ordinary private content creation and consumption of existing public references do not require this staff workflow. Discover a supported asset-publication operation through `scenario_tools_search` and read its schema. At authoring time no such MCP operation exists: `asset_update` exposes name, description, and tags, not visibility. Do not invent a `public` or `privacy` parameter, use `workflow_publish` for an asset, or substitute an undocumented API or browser flow. When publication is unavailable, complete staging and record `pending_publication` in the local receipt. Report the exact missing capability and the ready collection. Leave public `asset_id` fields absent from the shipped manifest; tag-based reuse and local building still work. This is a capability gap, not a request for the user to approve staging again. When a supported operation becomes available, publish the already-staged IDs through that operation. If the user has already published them, proceed directly to verification. Confirm public visibility and `asset_get` access using a user-authorized scope outside the owning team; no batch download is needed. A scope merely appearing in `teams_list` is not authorization to use it for this check. Failed retrieval leaves verification incomplete. For model-input templates, use the published IDs directly in the next agreed validation run; do not add generations solely to recheck publication. Successful reference use verifies that path, while creative quality is graded separately. Only after public access is verified, add each `asset_id` to its file entry in the owning skill's JSON manifest. Identical files can share an ID. Keep asset records in JSON and usage instructions in Markdown. Report publication and validation separately, with the manifest path, verification outcome, and any remaining gap. Never call an uploaded private collection published.
-
-
SKILL.md 25.4 KB
--- name: scenario description: 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. license: MIT --- # Scenario ## 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](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 (setup reference) before anything else is debugged; `diagnostics_run` names the failing layer (`auth`, `tenant-scope`, `api-unreachable`, or `healthy`). The default toolset is wider than the loop below: `asset_get`, `job_get`, `jobs_list` and `models_list` are called directly too. `?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}`. Every input goes inside `parameters`, `dry_run` and scope ids included (nearly every catalog tool declares scope ids; `plan_generation` takes only `description`), though direct tools take scope, and `model_run` its `dry_run`, at top level. The executor drops a misplaced key without an error: `arguments` surfaces as a false scope error, and a top-level `dry_run` runs the job, billed. 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, 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 | 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`. A stated preference ("always 9:16") goes to catalog `memory_set` with `scope` `project_user_memory` (`user_memory` across projects): it replaces the whole layer, so `memory_get` and merge first, and it runs on the delete lane. ## Worked example Generating a stylized game prop image: 1. `recommend` with the user's own words as `prompt` when the need is a capability; `search` with `target="models"`, `query="flux"`, `public=true` when you have a name. `search` ranks by keyword and its `filters` hold no capability key, so a capability-worded query can rank the wrong output type first. For the user's own trained models, omit `public` on `search` (`search` has no private flag); on `recommend` the flag is `include_private_models: true`. Re-discover ids each time: availability differs per team. 2. `model_schema_get` on the pick: exact field names, types, required flags, defaults, and caps such as the prompt's `max_length` (an overrun is a 400, never a trim). File fields take asset ids even when named `...Url`, and `cost_impact: true` flags what moves the price. 3. If the schema carries `runs_as` (`"lora"` or `"composition"`), never send that model's own id to `model_run`. Its `run_with.required_arguments` holds the real call: `model_id` there is the base model, and its `parameters` (the `loras` or `modelId` wiring) merge into inputs from the same schema. Sending `required_arguments` alone discards your prompt. 4. Optional: `prompt_spark` rewrites a thin prompt into an on-model one; pass the discovered id (a LoRA's own, not its base) and the draft `prompt`. Skip deliberate prompts. 5. `model_run` with `model_id` and schema-conformant `parameters`. If cost matters (the default assumption unless the user says otherwise), price first with `dry_run: true`, a top-level argument beside `model_id`: no job is created and the response's `creativeUnitsCost` is the exact payload's price; a `recommend` cost quote assumes defaults, and the schema's `cost_impact` fields move the real number. Then run: asset_ids come back, or a `job_id` for `jobs_wait`. Its `status` is `in_progress` after a timed-out wait, or with `wait=false` the backend's word at creation (`queued`, `in-progress`, `warming-up`); none is a different state. 6. `jobs_wait` with `job_ids=[...]` (up to 32); each completed row carries `assetIds` and `cuCost`, so no `job_get` follow-up; on timeout re-call with the returned `pending_job_ids` as `job_ids`. Failed jobs are reimbursed, except xAI generations stopped by moderation. 7. `asset_display` shows the asset inline; its `format` picks the rendering: `display` (the default) returns an inline image plus links and viewer data; `viewer` returns a lighter payload for a host that renders the interactive widget; `json` and `markdown` return metadata and links. `display` does not disable a host's widget. For PNG files, use `asset_download` with `format: "png"`, one call per asset. `asset_download` returns a file URL (save with `curl -L`, it may redirect). `format` converts images (`png`, `webp`, `jpg`, and `gif` to keep an animated GIF animated: the `png` default flattens it to one frame) and meshes (`glb`, `fbx`, `obj`); omit it for video and audio. When `curl` reports `host_not_allowed` or `CONNECT tunnel failed, response 403` while 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](https://mcp.scenario.com/docs/troubleshooting#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 the `app_url` that `asset_display` returns, where the user downloads directly and, for a mesh, picks the 3D export format the app offers, none of which `asset_download` converts to. The signed URL expires: 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`. A phone's HEIC photo uploads as is (`content_type: "image/heic"`), so do not convert it first; `Unhandled image format` lists what `kind: "image"` accepts, so convert a file of any other type locally. 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. `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`. It takes any number of ids (it batches them by 49 itself) and skips an id already filed, listing it under `already_present_asset_ids` beside `added_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, since `search` `filters={"collection_ids": [...]}` lags on a fresh write. `asset_add_tags` is additive, one `asset_id` per call, and a tag the asset already carries returns 400 `Duplicated tag`: read `tags` via `assets_get_bulk` first and send only the missing ones. For reusable templates or reference content, follow the [shared asset lifecycle](references/shared-assets.md): 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 `custom-generation`, or naming a quota, balance, or seat limit | `actionLimit` is the period's CU allowance (Free: 50 a day) and `actionValue` what is already consumed; a run priced above the gap is refused, so a low `actionValue` means this payload costs too much, not that credits ran out. Stop the batch, `dry_run` the payload, offer a cheaper member or fewer outputs, or report the reset or upgrade (unattended: report and stop). `usage` takes the same `project_id` (or `scope: "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 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` | Never infer its behavior from the name: report it 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 and `glb`/`fbx`/`obj` meshes: omit it for video, 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 - Pricing a later step with a placeholder id (`asset_placeholder`): file ids resolve before pricing, so the `dry_run` returns 404 and quotes nothing. Quote it from `recommend`, then `dry_run` once the real asset exists. - A bare value where the schema says `array: true`: silently dropped, the run ignoring your reference or LoRA. `asset_get` on the output echoes what the run consumed (`metadata.referenceImages`, `parentId`), the cheapest proof it was not. - Taking `recommend`'s `ranked[0]` blindly: read `next_step.type` first. On `ask_user`, present the options; the user's pick wins (non-interactive: task instructions, else `proceed`). On `proceed`, prefer `specialty.model_id`, else the first `ranked` entry its own text does not mark deprecated, reading `tradeoff` and `explanation` as well as `caveats`, since the flag lands in any of them: the ranking is by measured performance, not by lifecycle, so a deprecated member can top it. - Debugging blind: the `diagnose` MCP prompt (or `diagnostics_run` with the scope pair the failure happened under and any `mcpt_` ids seen as `observed_trace_ids`) returns trace ids; `usage` answers credit questions. - Guessing a file field's name: 400 `Input image is required` (or `images`, `referenceImages`, `startImage`, `frontImage`, `video`, `model`, each a different model's name for the same idea) means the payload never carried the field the schema's `required` list names, so copy that name verbatim; a URL in a file field reads as missing too, since file fields take asset ids only. - Feeding an image field a video, audio, or 3D asset: 400 `Unhandled image format` lists the image types it accepts. `asset_get` reports the asset's `mimeType`; pull a still with the clip's `firstFrame` when the model wants an image. - `filters.kind` on `search` with `target="models"`: a 400 `"kind" is not a filterable field`, since `kind` describes assets. Narrow models with `filters.type` (the architecture) or `tags`, or go through `recommend` for a capability. - Filtering `models_list` by `type` without `privacy: "public"`: a 400 says so. A private listing narrows by `status` or `modality`, or goes through `search` (private by default).
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.