Before you start
You need a Scene Assembly account with at least one provider key connected. Sign in at https://sceneassembly.mzork.com/account and add a key under your provider connections. The agent can use only the providers whose keys are connected there, and it cannot add or read keys itself.
The MCP URL
https://mcp-sceneassembly.smoxu.com/mcp
The Connected agents panel on your account page (https://sceneassembly.mzork.com/account) shows the same URL with a copy button.
Connect your client
Each client opens a Scene Assembly page in your browser the first time it connects. Sign in if asked, choose a spend limit and two permissions (see What you approve), and approve. Finish in the browser that opened the page: a link copied to another browser will not complete the connection.
Claude Code
claude mcp add --transport http scene-assembly https://mcp-sceneassembly.smoxu.com/mcp
Then run /mcp inside Claude Code, choose scene-assembly and sign in.
Claude.ai and Claude Desktop
Custom connectors are available in Claude on the web and in Claude Desktop. On a Free, Pro or Max plan (Free allows one custom connector):
- Open Customize → Connectors (claude.ai/customize/connectors).
- Click +, then Add custom connector.
- Paste the MCP URL and click Add. Leave Advanced settings empty: Scene Assembly registers the client itself.
- Connect it, and Claude opens the Scene Assembly page.
On a Team or Enterprise plan, an Owner adds the connector first: Organization settings → Connectors (claude.ai/admin-settings/connectors), Add, hover over Custom and choose Web, paste the MCP URL and click Add. Each member then opens Customize → Connectors, finds the connector and clicks Connect.
Cursor
Add the server to ~/.cursor/mcp.json, or to .cursor/mcp.json in a project:
{
"mcpServers": {
"scene-assembly": {
"url": "https://mcp-sceneassembly.smoxu.com/mcp"
}
}
}
Then follow Cursor's prompt to log in to the server, which opens the Scene Assembly page.
Any other client
The server speaks Streamable HTTP and uses OAuth 2.1 with dynamic client registration and PKCE. A client that supports remote MCP servers with OAuth needs only the URL: an unauthenticated request gets a 401 whose WWW-Authenticate header points at the protected-resource metadata, and discovery goes from there.
Reconnecting
A connection lasts 30 days from the day you approve it and is not extended by use, so expect to reconnect about once a month. When it lapses, the agent's calls fail with HTTP 401; connect again the same way you did the first time. Each approval creates a new entry in the Connected agents panel with its own limit; the lapsed one leaves the panel on its own. Each entry shows when it was connected and last used, which tells a working connection from an old one with the same name.
What you approve
The consent page shows the name the client gave itself, marked unverified, and the address it returns to. It lists what the agent will be able to do: start image and video jobs with your connected keys; cancel, resume or stop tracking jobs, including ones you started in the browser; copy files from any web link into your account's temporary storage; see your jobs, library and spend; and read your providers' remaining balances. It cannot see or change your provider keys.
You choose three things:
- Spend limit per 24 hours. In US dollars, from $0.50 to $500. The default is $5. A job that would go over the limit is refused before it starts.
- Allow models without a published price. Off by default. Some models have no price Scene Assembly can know before they run: every model from Kie.ai, Runware, CometAPI, Cloudflare and Pollinations, and some fal.ai and Atlas Cloud models. These runs count toward the limit only once the provider reports what they cost, so the limit cannot stop them in advance. That is why the default is off. The consent page names the connected providers this affects.
- Allow deleting files from your library. Off by default. Without it the agent is not offered
delete_assetat all.
To change these later, open the Connected agents panel on your account page, edit the limit or the switches, and save. The change applies to the agent's next call, though a client may keep showing an old tool list, with or without delete_asset, until it reconnects. Disconnect is in the same panel and also takes effect on the agent's next call, which fails with HTTP 401. Jobs the agent already started keep running and stay in your library.
The usual loop
Each step is one tool call. The arguments are examples.
See what can run.
list_modelswith no arguments returns a compact list of the models your connected providers offer. A model the vendor has deprecated still runs. The list setsdeprecatedto true on it and leaveslabelas the name a saved job already uses.{}Read one model's settings.
list_modelswithproviderandmodelIdreturns each input mode with its fields, options, bounds and defaults, and how many reference images it takes.{ "provider": "gemini", "modelId": "gemini-nano-banana-2.1" }Price it.
estimate_costtakes the same fields asgenerateand checks the result against the agent's limit. Settings you leave out take the model's defaults, and the response echoes them invalues.{ "provider": "gemini", "modelId": "gemini-nano-banana-2.1", "mediaType": "image", "inputMode": "text", "prompt": "A paper kite over a harbour at dawn", "values": { "aspectRatio": "16:9" } }Start the job.
generatewith anidempotencyKey. It returns at once with ajob, the estimate and the agent's remaining budget.{ "provider": "gemini", "modelId": "gemini-nano-banana-2.1", "mediaType": "image", "inputMode": "text", "prompt": "A paper kite over a harbour at dawn", "values": { "aspectRatio": "16:9" }, "idempotencyKey": "harbour-kite-1" }Wait for it.
get_jobwithwaitSeconds(up to 25) holds the call until the job's state changes. Call it again until the state issaved,failed,cancelledorneeds_attention. A saved job lists itsoutputs, each with anassetIdand a download link.{ "jobId": "<job.id from generate>", "waitSeconds": 25 }Look at the result.
view_assetreturns an image inline as a WebP no larger than 1024 px, with a download link. A video comes back as the link only.{ "assetId": "<outputs[0].assetId from get_job>" }
Idempotency
The idempotencyKey is any string you choose, up to 200 characters, and it belongs to this agent only. Reuse the key when you retry the same request, for example after a timeout: you get the same job back and are not charged again. A new key, or no key at all, starts a new job and a new charge. Reusing a key with a different request, or with a job that was removed, is refused with token_conflict. If the first call with a key is still starting, a second one is refused with request_in_progress; wait a few seconds and retry, or call list_jobs. Both refusals are described under Errors.
References
Some input modes take reference images, and edit mode takes a source video. Models that list reference video or audio (Seedance 2.5 on Runware, and the Atlas Seedance reference-to-video models) also take referenceVideos and referenceAudios on generate. Stage a file with add_reference, then pass the uploadId it returns.
From a link. Scene Assembly downloads the file straight away.
{ "url": "https://example.com/cat.png" }
- The URL must be public
https, with a hostname rather than an IP address and no user name or password in it. - It cannot point at Scene Assembly itself. Pass
{ "assetId": … }for a file already in the library. - Up to 3 redirects are followed, each checked by the same rules. The download times out after 20 seconds.
- The type is read from the file's bytes, not its headers: PNG, JPEG, WebP or AVIF images up to 20 MB, an edit-source MP4 up to 200 MB, WebM up to 100 MB, and MP3 or WAV audio up to 15 MB. WebM staged from a link is an edit source only. A reference video is MP4 up to 100 MB, or a MOV uploaded with
video/quicktime. QuickTime and HEIC files are refused from a link, because those brands are not stored from a sniff.
From a file you have. Ask for an upload link with the file's type and its exact size in bytes:
{ "upload": { "mimeType": "image/png", "bytes": 482113 } }
The response has a putUrl. PUT exactly that many bytes there within 15 minutes, with the same Content-Type:
curl -T cat.png -H "Content-Type: image/png" "<putUrl>"
A different type or byte count is refused by the upload link itself with an HTTP 400, not by a tool; ask for a new link with the right figures.
mimeType is one of image/png, image/jpeg, image/webp, image/avif, video/mp4, video/webm, video/quicktime, audio/mpeg, audio/mp3, audio/wav, audio/wave or audio/x-wav. Images stop at 20 MB, audio at 15 MB, and video at 100 MB. video/webm is an edit source. A reference video is video/mp4 or video/quicktime.
From a YouTube video. For edit mode, Scene Assembly can cut a section of a YouTube video into the source clip:
{ "youtube": { "url": "https://youtu.be/aqz-KE-bpKQ", "startSeconds": 62, "endSeconds": 74.4, "provider": "runware", "modelId": "bytedance:seedance@2.5" } }
- Times are seconds to one decimal place. With
providerandmodelId, the section must fit that model's edit source length (list_models); without them, 1–30 seconds. - Only that section is downloaded, at 720p or above, and staged as an MP4 for 24 hours.
- Add
"saveToLibrary": trueto also keep it in your library. The response then hasassetIdandsavedToLibrary: true; pass{ "assetId": … }assourceVideofor any later edit instead of importing again. The saved clip counts against your library storage. If the library is full the import still succeeds for this edit, withsavedToLibrary: falseand alibraryNoticesaying so. Inlist_assetsa saved clip hasorigin(site,videoId,startSeconds,endSeconds,title). - The response gives the clip's
durationSeconds,widthandheight; check them against the model before callinggenerate. - Private, members-only, age-restricted and live videos cannot be imported. An account can import 10 sections an hour, one at a time, shared with the browser.
- The call returns when the clip is ready, usually within 40 seconds and up to about 2 minutes, so set your client's tool timeout to at least 130 seconds (many default to 60).
Using it. An uploadId lasts 24 hours. It is cleaned up soon after the jobs that used it finish, so stage the file again for a later job. A file already in your library needs no staging: pass its assetId. The refusals these steps can return are listed under Errors.
{ "provider": "gemini", "modelId": "gemini-nano-banana-2.1", "mediaType": "image", "inputMode": "image", "prompt": "The same kite, in watercolour", "references": [{ "uploadId": "<from add_reference>" }, { "assetId": "<from list_assets>" }], "idempotencyKey": "harbour-kite-watercolour" }
References go in order. list_models gives each mode's minimum and maximum count, and whether that mode accepts reference video or audio. In edit mode, pass the clip as sourceVideo instead of in references. Reference video and audio are separate arrays, not extra images. A reference video is MP4 or MOV. WebM is an edit source only. A Seedance draft may include them; finalize_draft does not resend them. An account can hold up to 32 staged references, 256 MB in all, across the browser and every agent.
Budget
- The window rolls. The limit covers the last 24 hours, counted back from each call, not a calendar day.
- A job is charged when it is accepted, at its estimate, before the provider starts it. When the job finishes, the charge settles to the cost recorded in your spend ledger.
- A cancel releases the charge only when the provider never ran the job.
cancel_jobworks only while a job is still queued. A job that fails, or that you stop tracking, keeps its charge, because the provider may have billed for it. - Models without a published price book $0 when they start, and count at their real cost once the provider reports it. They run only if you allowed them.
- When a job would not fit,
generateis refused withbudget_exceeded, androomAtsays when enough earlier charges leave the window.estimate_costanswers the same question without starting anything. get_spendreturns the account's spend totals (browser runs included), this agent's limit, what it has used and what is left, and optionally the latest ledger entries and live balances from providers that publish them.
Tools
The server offers 14 tools. delete_asset is offered only when you allowed the agent to delete files.
| Tool | What it does | Key inputs |
|---|---|---|
list_models |
Models your connected providers can run. Filter by provider or model to get each mode's settings. | provider, modelId, mediaType, inputMode (all optional) |
estimate_cost |
Prices a request and checks it against the agent's limit, without running it. | The same fields as generate |
add_reference |
Stages a reference image, reference video, reference audio, or a source video for edit mode (from a file, a link, or a section of a YouTube video), and returns an uploadId. |
url, upload with mimeType and bytes, or youtube with url, startSeconds and endSeconds |
generate |
Starts an image or video job and charges its estimate to the agent's limit. Returns at once. Seedance 2.5 draft is values.draft: true and is off unless you set it. |
provider, modelId, mediaType, inputMode, prompt, values, references, referenceVideos, referenceAudios, sourceVideo, idempotencyKey |
finalize_draft |
Renders one Seedance 2.5 draft at 1080p. A new job and a separate charge. Do not resend the prompt or media. With jobId, the length is the one saved on that draft; a different durationSeconds is refused. |
jobId, or draftId plus durationSeconds and inputMode, idempotencyKey |
get_job |
A job's state and, once saved, its outputs with download links. A Seedance draft includes draftId. Can wait for a change. |
jobId, waitSeconds (0 to 25) |
list_jobs |
The account's recent jobs, newest first, including ones started in the browser. | state (active, needs_attention, finished or all), limit (up to 100) |
cancel_job |
Cancels a job that is still queued and releases its charge. | jobId |
resume_job |
Tries again on a job that needs attention, when its failureReason can be fixed by trying again. At most three times. |
jobId |
dismiss_job |
Stops tracking a job that needs attention. The provider may still finish it and charge for it. | jobId, remove (also takes it off the list) |
list_assets |
The library's saved images and videos, newest first, 50 at a time, with the prompt, provider and model that made each one. | kind, temporaryOnly, cursor |
view_asset |
One saved file: an image inline as a WebP up to 1024 px plus a download link, or a video's download link. | assetId |
delete_asset |
Permanently deletes a file from the library. Hidden unless you allowed it. | assetId |
get_spend |
Account spend totals, this agent's limit and what is left, and optionally ledger entries and provider balances. | entries (up to 50), balances |
Each agent can make 300 read calls, 60 write calls (add_reference, cancel_job, dismiss_job, delete_asset) and 10 submissions (generate, finalize_draft, resume_job) per minute. An account runs at most 10 jobs at a time, shared with the browser.
Seedance drafts
Seedance 2.5 on Runware can render a 480p draft (values.draft set to true on generate; the default is off) and later render that same preview at 1080p with finalize_draft. get_job and view_asset return draftId when the preview has one. Finalize does not take a prompt, references, reference video, reference audio, or a source video — those stay on the draft — and it is budget-checked on its own, using the documented 1080p rate. Runware does not publish a separate finalize price, so that figure is an estimate until the provider reports a cost.
Pass jobId and the length is taken from that draft (durationSeconds, or sourceDurationSeconds on an edit). You can omit durationSeconds. Passing a length more than a second off the saved one is refused, and nothing is reserved. A bare draftId still needs durationSeconds and inputMode, because there is no saved job to read.
An edit draft is priced from the source clip. The Worker reads the duration out of the file before it reserves. sourceDurationSeconds is optional and, when you pass it, must be within a second of that length. If the length cannot be read, the draft is refused instead of priced as unknown.
Errors
A refusal arrives as a normal tool result with isError: true. Its structuredContent is { code, message, retryable, … }: message is a sentence you can pass on, retryable says whether sending the same call again later can succeed, and some codes carry more fields. Read code, not the message.
An HTTP 401 from the server is not a refusal. It means the connection was disconnected from the Connected agents panel or has expired, and the person has to reconnect from their client.
Requests and settings
| Code | Retryable | What it means and what to do |
|---|---|---|
invalid_field |
No | An argument is wrong. field names it, and allowed, min or max say what it accepts. Fix it, calling list_models for the model's settings. |
invalid_settings |
No | The provider's own check rejected the settings. Compare them with list_models for that model and mode. |
content_floor |
No | The request violates a content restriction that applies to every filter level. Explain the refusal; do not retry the same request or try another filter level. |
invalid_request |
No | The request was malformed, for example a cursor that did not come from list_assets. |
invalid_references |
No | The same reference was passed twice, or a file is in the wrong role: images go in references, reference videos in referenceVideos, reference audio in referenceAudios, and an edit source in sourceVideo. WebM passed as a reference video is refused: it is an edit source. A clip over that model's own size or length is refused before the job is reserved. |
invalid_token |
No | Scene Assembly built an invalid submission token. This should not happen; report it. |
not_found |
No | No job or file with that id on this account. |
draft_unavailable |
No | That job is not a Seedance 2.5 draft, or it has no draftId yet. Generate a 480p draft and wait until get_job returns one. |
local_fixture_mode |
No | Only on a local development server with fake generation switched on, which runs image jobs and edits only. |
Providers
| Code | Retryable | What it means and what to do |
|---|---|---|
connection_required |
No | The account has no key for that provider. Ask the person to connect one on their account page; an agent cannot add keys. |
provider_unavailable |
No | Background generation is not available for that provider. Pick another one from list_models. |
Budget and limits
| Code | Retryable | What it means and what to do |
|---|---|---|
budget_exceeded |
No | The job's estimate does not fit in what is left of the 24-hour limit. Carries estimateUsd, limitUsd, usedUsd, remainingUsd and roomAt: the time, in milliseconds since the Unix epoch, when enough room frees up, or null when this job costs more than the whole limit. Tell the person rather than retrying sooner. |
cost_unknown |
No | The model has no published price and the person has not allowed unpriced models. Choose a priced model, or ask the person to allow them in the Connected agents panel. |
rate_limited |
Yes | This agent is calling too often. Wait retryAfterSeconds, then retry. |
active_jobs |
No | The account already has 10 jobs running. Wait for one to finish (list_jobs with state: "active"), or cancel one. |
service_capacity |
Yes | Background generation is busy for everyone. Try again shortly. |
capacity |
No | Either the account's storage has no room for this job's output, or the job could not be started for another reason. Read the message: if it says the storage is full, delete files from the library or ask the person to; if it says to try again shortly, wait and retry once. |
reserved_capacity |
No | Jobs in progress are holding the rest of the account's storage. Wait for them to finish. |
temporary_results |
No | The account has results saved only temporarily because its storage was full. Download and delete them (list_assets with temporaryOnly: true), or wait for them to expire, before starting another job. |
Idempotency
| Code | Retryable | What it means and what to do |
|---|---|---|
token_conflict |
No | This idempotencyKey was already used for a different request, or for a job that was removed. Use a new key. |
request_in_progress |
Yes | A call with this idempotencyKey is still starting. Retry in a few seconds, or call list_jobs. |
References
| Code | Retryable | What it means and what to do |
|---|---|---|
reference_fetch_failed |
No | A reference URL was refused or could not be downloaded: not https, an IP address, a Scene Assembly address, too many redirects, an error status, a type other than PNG, JPEG, WebP, AVIF, MP4 or WebM, or a file over the size limit. The message says which. |
invalid_upload |
No | add_reference was asked for an upload of an unsupported type or size. |
upload_size |
Yes | A library file passed as { "assetId": … } did not copy intact for the job. Retry the same generate call, with the same idempotencyKey. |
input_capacity |
No | Too many staged references are still in use: an account holds up to 32, 256 MB in all. Wait for the jobs using them to finish. |
reference_unavailable |
No | A staged upload has expired, was never finished, or was already cleaned up, or a library file cannot be used as a reference (too large or the wrong type). Stage it again. |
inline_input_size |
No | Gemini and CometAPI take up to 12 MB of reference images per job in all. Use smaller images. |
invalid_range |
No | A YouTube section is outside the model's source length, is not to a tenth of a second, runs past the end of the video, or the cut clip came out larger than 100 MB. |
source_unavailable |
No | The YouTube video is private, members-only, age-restricted, live or a premiere, has no version at 720p or above, or the clip does not meet the model's source size. The message says which. |
source_refused |
Yes | YouTube refused the download, all importers are in use or not available, the import took too long, or the section could not be cut cleanly. Wait retryAfterSeconds, then retry. |
import_rate_limited |
Yes | This account has imported 10 YouTube sections in the last hour, or another import is still running. Wait retryAfterSeconds. |
Job control
| Code | Retryable | What it means and what to do |
|---|---|---|
generation_started |
No | cancel_job came too late: the provider already has the job. It stays tracked; follow it with get_job. |
tracking_state_changed |
No | The job is no longer waiting for a decision. Call get_job to see where it is. |
unrecoverable |
No | Trying again cannot fix this job. Stop tracking it with dismiss_job. |
resume_exhausted |
No | The job was already resumed three times. Stop tracking it with dismiss_job. |
reconciliation_required |
No | Scene Assembly cannot tell yet whether the provider ran this job, so it cannot be resumed. Leave it, or stop tracking it with dismiss_job. |
internal_error |
Yes | Something failed inside Scene Assembly. Try again shortly. |
Security and privacy
- Sign-in is OAuth 2.1. Your client gets its own access token, valid for an hour and refreshed for up to 30 days. It is tied to your account and to this one connection, and disconnecting stops it on the next call.
- Keys stay on the server. Jobs run with the provider keys stored on your account. No tool returns a key, and an agent cannot add, read or change one.
- Download links are short-lived and open. A link from
get_joborview_assetexpires after ten minutes, and anyone holding it can download the file until then. Do not paste links anywhere public; call the tool again for a fresh one. - Upload links are credentials too. A
putUrlaccepts one file for 15 minutes from whoever holds it. - Links you pass are fetched by Scene Assembly's server, with no cookies or credentials of yours, and never from Scene Assembly's own addresses.
- An agent sees the whole account's work: your jobs, prompts, library and spend, including what you made in the browser.