SETUP GUIDE

Connect an agent to Scene Assembly

Scene Assembly runs an MCP server, so an AI agent such as Claude Code, Claude or Cursor can generate images and video with the provider keys connected to your account. Every result lands in your library, each agent has a spend limit you set, and the agent never sees your keys.

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):

  1. Open Customize → Connectors (claude.ai/customize/connectors).
  2. Click +, then Add custom connector.
  3. Paste the MCP URL and click Add. Leave Advanced settings empty: Scene Assembly registers the client itself.
  4. 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_asset at 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.

  1. See what can run. list_models with no arguments returns a compact list of the models your connected providers offer. A model the vendor has deprecated still runs. The list sets deprecated to true on it and leaves label as the name a saved job already uses.

    {}
    
  2. Read one model's settings. list_models with provider and modelId returns 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" }
    
  3. Price it. estimate_cost takes the same fields as generate and checks the result against the agent's limit. Settings you leave out take the model's defaults, and the response echoes them in values.

    { "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" } }
    
  4. Start the job. generate with an idempotencyKey. It returns at once with a job, 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" }
    
  5. Wait for it. get_job with waitSeconds (up to 25) holds the call until the job's state changes. Call it again until the state is saved, failed, cancelled or needs_attention. A saved job lists its outputs, each with an assetId and a download link.

    { "jobId": "<job.id from generate>", "waitSeconds": 25 }
    
  6. Look at the result. view_asset returns 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 provider and modelId, 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": true to also keep it in your library. The response then has assetId and savedToLibrary: true; pass { "assetId": … } as sourceVideo for 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, with savedToLibrary: false and a libraryNotice saying so. In list_assets a saved clip has origin (site, videoId, startSeconds, endSeconds, title).
  • The response gives the clip's durationSeconds, width and height; check them against the model before calling generate.
  • 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_job works 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, generate is refused with budget_exceeded, and roomAt says when enough earlier charges leave the window. estimate_cost answers the same question without starting anything.
  • get_spend returns 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_job or view_asset expires 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 putUrl accepts 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.