Scenario workflows skill
Use when a task involves running a Scenario workflow through MCP, including anything the user calls a Scenario app, a saved pipeline, or a multi-step generation graph.
by scenario-labs·MIT license·★ 854 Stars on the repo·GitHub ↗
npx degit scenario-labs/skills/skills/scenario-workflows#main ~/.claude/skills/scenario-workflowsChecked ·commit main
Files of Scenario workflows
Show the full text60 lines
Scenario Workflows
Overview
A Scenario workflow is a saved node graph chaining several models into one call; users say "app" and mean one whose status is ready. workflow_run returns a job tracked like any other generation.
Only workflows_list, workflow_get and workflow_run are listed by default; approve and reject run through scenario_tools_search plus their executor lane. Connection and the core loop: the scenario skill. Creating, editing and publishing graphs: the scenario-workflow-authoring skill. If a sibling skill named here is missing from your available skills, ask the user to install it (npx skills add scenario-labs/skills --skill <name>); unattended, proceed from tool schemas and flag the gap.
Quick reference
| Step | Call | Notes |
|---|---|---|
| 1. List | workflows_list status="ready" |
Always cap limit |
| 2. Read the contract | list inputs[], get workflow.inputs_definition |
name is the run key |
| 3. Price and validate | workflow_run with dry_run=true |
Returns cost, creates no job |
| 4. Run | workflow_run with inputs |
Returns a job |
| 5. Wait | jobs_wait |
Re-call with pending_job_ids |
Ids are prefixed wflow_; copy them from workflows_list or search, never construct them. For a named app or a public template, call search with target="workflows", a keyword query, limit=3, and your scope; add public=true for the public catalog. Workflows support keyword text and filters, not image or semantic search. To find ready apps, add the raw filter: 'status = "ready"' (not filters.status). Read hits from workflows, then call workflow_get on the chosen id and read the contract from workflow.inputs_definition: search summaries contain neither the run inputs nor the editable graph. Search uses offset, not page_token; heed any _hint on an empty page instead of repeating the same search.
Cap every list call
Each record carries the compiled flow and the whole editorInfo node graph, and the tool exposes no compact flag: the two fields made up three quarters of the bytes in live records, which ran from about 1,900 to 22,000 characters, and a draft with flow: [] still ships its editorInfo. Cap limit at 3 or fewer, read only id, name, hasFlow and inputs, and page with page_token set to the previous reply's nextPaginationToken, absent on the last page. The tool schema advertised a ceiling of 200 at authoring time while the API rejects anything past 100 with a 400 naming pageSize and the range [1; 100], so the schema's maximum is not a value to send.
Only draft and ready filter server-side; other statuses filter each page client-side (flagged _workflowListStatusFilter); there an empty page beside a nextPaginationToken means keep paging.
The input contract
workflow_run's inputs object is keyed by inputs[].name, taken verbatim from the record. Each name is the id of the node behind it, so names can be positional (text2, text3), neither contiguous nor ordered.
labelanddescriptioncarry the human intent, not the key.- Never harvest keys from
editorInfo.nodes[].data.name; node names go stale. requiredis an object: testrequired.always === true, a truthiness check reads{"always": false}as required too.- Inputs are typed:
string,file,file_array,string_array, more.filetakes an asset id (upload first). Match the type: the API drops scalar-for-array mismatches silently and still charges;workflow_runwraps simple scalars into arrays, but only simple ones. inputs[]is not the whole contract: a string input inherits the length ceiling of the node behind it (a model's own prompt cap, observed as low as 4000 characters), which the record never lists and which surfaces only at run time as a 400 quoting the cap but not the input key. The dry run enforces the same validator at zero cost: route long texts throughdry_run=trueand shorten to fit rather than retrying verbatim, and with several string inputs bisect with dry runs to find the offender.workflow_getwrapsinputs_definition/editor_infoinworkflow;workflows_listwrapsinputs/editorInfoinworkflows.
Worked example: run a saved app
workflows_listwithstatus="ready",limit=3, plusteam_idandproject_id. Readid,nameandinputsoff each record; ignoreflowandeditorInfo.- An
inputs[]entry{"name": "text1", "required": {"always": false}}runs with{"text1": "..."}. workflow_runwithworkflow_id,dry_run=true, and the fullinputsobject. The reply iscreativeUnitsCost,creativeUnitsDiscountand an emptyjob; quote the cost first. It also runs the real validator.- Repeat without
dry_run, thenjobs_waiton the returned job, re-calling withpending_job_idswhile it runs. asset_displayeach output asset, one per id.
Common mistakes
- Guessing input keys from labels or the node graph instead of reading
inputs[]. - Skipping the dry run: two of three ready workflows failed its validation with correctly named inputs; report that error, not the payload.
- Reading cost or attribution off the parent job: a finished workflow job bills
cuCost: 0and listsassetIdswith no per-model attribution. Every node ran as its own child job carrying the realcuCost(together they equal the dry-run quote, except inside aforEachloop, whose dry run prices one iteration), andjob_getverbose=trueon the workflow job returnsmetadata.flowmapping each node to its childjobId,modelIdand output asset ids. - Sending a batch through a
file_arrayinput and expecting one output per item: whether the app loops is in its graph, not its inputs. Look for aforEachnode ahead of the model ineditorInfo(for-eachin the compiledflow; theworkflows_listrecord carries both, orworkflow_get); without one, the app makes one generation from all the items, so callworkflow_runonce per item (each its own dry run and job). A looping app's dry run prices one iteration, so multiply its quote by the item count. - Running a draft:
flow: []andhasFlow: falsewhileinputslooks complete. Publishing: seescenario-workflow-authoring. - Calling
workflow_approveorworkflow_rejectwithout all three ofworkflow_id,workflow_job_idandnode_id: the gate is per node; a parked run never finishes on its own. Find the run withjobs_list, read it withjob_getverbose=true(compact replies omitmetadata):metadata.flowlists per-node statuses, the pending approval node's id isnode_id, the job's id isworkflow_job_id, itsworkflowIdtheworkflow_id. Reject cancels the run.
| 1 | |
| 2 | name scenario-workflows |
| 3 | description "Use when a task involves running a Scenario workflow through MCP, including anything the user calls a Scenario app, a saved pipeline, or a multi-step generation graph. Triggers include listing workflows, building workflow_run's inputs object, pricing a run with a dry run, approving or rejecting a stuck approval node, a run rejected for input length, or a workflows_list reply flooding the context. Creating or editing graphs is scenario-workflow-authoring. Keywords: workflow, app, approval gate." |
| 4 | license MIT |
| 5 | |
| 6 | |
| 7 | # Scenario Workflows |
| 8 | |
| 9 | ## Overview |
| 10 | |
| 11 | A Scenario workflow is a saved node graph chaining several models into one call; users say "app" and mean one whose status is `ready`. `workflow_run` returns a job tracked like any other generation. |
| 12 | |
| 13 | Only `workflows_list`, `workflow_get` and `workflow_run` are listed by default; approve and reject run through `scenario_tools_search` plus their executor lane. Connection and the core loop: the `scenario` skill. Creating, editing and publishing graphs: the `scenario-workflow-authoring` skill. If a sibling skill named here is missing from your available skills, ask the user to install it (`npx skills add scenario-labs/skills --skill <name>`); unattended, proceed from tool schemas and flag the gap. |
| 14 | |
| 15 | ## Quick reference |
| 16 | |
| 17 | | Step | Call | Notes | |
| 18 | | --------------------- | ------------------------------------------------- | ------------------------------ | |
| 19 | | 1. List | `workflows_list` `status="ready"` | Always cap `limit` | |
| 20 | | 2. Read the contract | list `inputs[]`, get `workflow.inputs_definition` | `name` is the run key | |
| 21 | | 3. Price and validate | `workflow_run` with `dry_run=true` | Returns cost, creates no job | |
| 22 | | 4. Run | `workflow_run` with `inputs` | Returns a job | |
| 23 | | 5. Wait | `jobs_wait` | Re-call with `pending_job_ids` | |
| 24 | |
| 25 | Ids are prefixed `wflow_`; copy them from `workflows_list` or `search`, never construct them. For a named app or a public template, call `search` with `target="workflows"`, a keyword `query`, `limit=3`, and your scope; add `public=true` for the public catalog. Workflows support keyword text and filters, not image or semantic search. To find ready apps, add the raw `filter: 'status = "ready"'` (not `filters.status`). Read hits from `workflows`, then call `workflow_get` on the chosen id and read the contract from `workflow.inputs_definition`: search summaries contain neither the run inputs nor the editable graph. Search uses `offset`, not `page_token`; heed any `_hint` on an empty page instead of repeating the same search. |
| 26 | |
| 27 | ## Cap every list call |
| 28 | |
| 29 | Each record carries the compiled `flow` and the whole `editorInfo` node graph, and the tool exposes no compact flag: the two fields made up three quarters of the bytes in live records, which ran from about 1,900 to 22,000 characters, and a draft with `flow: []` still ships its `editorInfo`. Cap `limit` at 3 or fewer, read only `id`, `name`, `hasFlow` and `inputs`, and page with `page_token` set to the previous reply's `nextPaginationToken`, absent on the last page. The tool schema advertised a ceiling of 200 at authoring time while the API rejects anything past 100 with a 400 naming `pageSize` and the range [1; 100], so the schema's maximum is not a value to send. |
| 30 | |
| 31 | Only `draft` and `ready` filter server-side; other statuses filter each page client-side (flagged `_workflowListStatusFilter`); there an empty page beside a `nextPaginationToken` means keep paging. |
| 32 | |
| 33 | ## The input contract |
| 34 | |
| 35 | `workflow_run`'s `inputs` object is keyed by `inputs[].name`, taken verbatim from the record. Each name is the id of the node behind it, so names can be positional (`text2`, `text3`), neither contiguous nor ordered. |
| 36 | |
| 37 | `label` and `description` carry the human intent, not the key. |
| 38 | Never harvest keys from `editorInfo.nodes[].data.name`; node names go stale. |
| 39 | `required` is an object: test `required.always === true`, a truthiness check reads `{"always": false}` as required too. |
| 40 | Inputs are typed: `string`, `file`, `file_array`, `string_array`, more. `file` takes an asset id (upload first). Match the type: the API drops scalar-for-array mismatches silently and still charges; `workflow_run` wraps simple scalars into arrays, but only simple ones. |
| 41 | `inputs[]` is not the whole contract: a string input inherits the length ceiling of the node behind it (a model's own prompt cap, observed as low as 4000 characters), which the record never lists and which surfaces only at run time as a 400 quoting the cap but not the input key. The dry run enforces the same validator at zero cost: route long texts through `dry_run=true` and shorten to fit rather than retrying verbatim, and with several string inputs bisect with dry runs to find the offender. |
| 42 | `workflow_get` wraps `inputs_definition`/`editor_info` in `workflow`; `workflows_list` wraps `inputs`/`editorInfo` in `workflows`. |
| 43 | |
| 44 | ## Worked example: run a saved app |
| 45 | |
| 46 | `workflows_list` with `status="ready"`, `limit=3`, plus `team_id` and `project_id`. Read `id`, `name` and `inputs` off each record; ignore `flow` and `editorInfo`. |
| 47 | An `inputs[]` entry `{"name": "text1", "required": {"always": false}}` runs with `{"text1": "..."}`. |
| 48 | `workflow_run` with `workflow_id`, `dry_run=true`, and the full `inputs` object. The reply is `creativeUnitsCost`, `creativeUnitsDiscount` and an empty `job`; quote the cost first. It also runs the real validator. |
| 49 | Repeat without `dry_run`, then `jobs_wait` on the returned job, re-calling with `pending_job_ids` while it runs. |
| 50 | `asset_display` each output asset, one per id. |
| 51 | |
| 52 | ## Common mistakes |
| 53 | |
| 54 | Guessing input keys from labels or the node graph instead of reading `inputs[]`. |
| 55 | Skipping the dry run: two of three ready workflows failed its validation with correctly named inputs; report that error, not the payload. |
| 56 | Reading cost or attribution off the parent job: a finished workflow job bills `cuCost: 0` and lists `assetIds` with no per-model attribution. Every node ran as its own child job carrying the real `cuCost` (together they equal the dry-run quote, except inside a `forEach` loop, whose dry run prices one iteration), and `job_get` `verbose=true` on the workflow job returns `metadata.flow` mapping each node to its child `jobId`, `modelId` and output asset ids. |
| 57 | Sending a batch through a `file_array` input and expecting one output per item: whether the app loops is in its graph, not its inputs. Look for a `forEach` node ahead of the model in `editorInfo` (`for-each` in the compiled `flow`; the `workflows_list` record carries both, or `workflow_get`); without one, the app makes one generation from all the items, so call `workflow_run` once per item (each its own dry run and job). A looping app's dry run prices one iteration, so multiply its quote by the item count. |
| 58 | Running a draft: `flow: []` and `hasFlow: false` while `inputs` looks complete. Publishing: see `scenario-workflow-authoring`. |
| 59 | Calling `workflow_approve` or `workflow_reject` without all three of `workflow_id`, `workflow_job_id` and `node_id`: the gate is per node; a parked run never finishes on its own. Find the run with `jobs_list`, read it with `job_get` `verbose=true` (compact replies omit `metadata`): `metadata.flow` lists per-node statuses, the pending approval node's id is `node_id`, the job's id is `workflow_job_id`, its `workflowId` the `workflow_id`. Reject cancels the run. |
| 60 |
Discussion
Alternatives
Browse more free Claude skills or everything in Operations.