Developer Guide

Use OpenStory from an agent

Connect an MCP client or script to OpenStory — inspect a production, edit a scene, plan and approve generation, poll it, and export

OpenStory has two agent surfaces:

  • MCP at /mcp — inspect a whole production, edit scenes, plan and run generation, and export. This is the one to use from Claude, Cursor or any MCP client.
  • REST at /api/v1 — create a sequence from a script in one call, then poll it. See Public API. Use it where MCP is not available, and for creating sequences (MCP cannot create one yet).

Connect

Every tool name has the openstory. prefix (openstory.get_scene); this guide drops it for brevity. Only whoami has none.

WhatValue
Server URLhttps://<your-openstory-host>/mcp (Streamable HTTP, POST only, no sessions)
Protocol revision2026-07-28, and the 2025 handshake for clients that still open with initialize. There is no session either way: each request is served on its own, so a request may reach any server instance. With the official SDK client, set versionNegotiation: { mode: 'auto' } (or pin 2026-07-28).
OAuth (preferred)Discovery at /.well-known/oauth-protected-resource/mcp; dynamic client registration; authorization code + PKCE; resource = https://<host>/mcp. An unauthenticated request gets a 401 with a WWW-Authenticate challenge pointing at that document.
Static headerAuthorization: Bearer osk_… with an API key from Settings → Developer. API keys are not scoped.
Getting a keyWithout a browser redirect, use the REST device-code login: POST /api/v1/device/code, show the user the code, poll the returned link until it returns the key.

Scopes (OAuth only)

ScopeGrants
sequences:readEvery read tool and resource.
sequences:writeupdate_scene, update_sequence, archive_sequence, unarchive_sequence, scene and shot create / reorder / delete / restore, update_shot, shot prompts, spec and dialogue edits, picking a still, clip, dialogue version or reading, character / location / element edits, voice and sheet picks, the music prompt and track, upload_media and the set_*_from_upload tools, select_sequence_model, Studio favourites and deletes, cancelling a video render, dialogue recording, voice design or pending prompt/still, plan_export, start_export.
generatecreate_sequence, Studio generation, edits, finals and prompt drafts, regenerate_storyboard, rebuild_shot_prompts, plan_generation, execute_generation, retry_failed_work, one shot's still, variants and clip, rendering drafts at quality, add_model_to_sequence, add_element / replace_element, sheets, recasts, voice design, music and dialogue — anything that can spend credits.

A missing scope is a tool error with code INSUFFICIENT_SCOPE and details.scope naming what to re-authorize with. Tool discovery and whoami need no scope.

Identifiers

  • sequenceId is required on every production tool; everything below it is checked against it.
  • A scene id (sceneId) and a shot id (shotId) are different things. get_scene takes a scene id and refuses a shot id; get_shot takes a shot id and refuses a scene id. A shot carries its parent sceneId, so you can always navigate back up.
  • What a scene or shot shows is its selected version (script, prompt, still, clip). Reads return the selected version and its id; update_scene asks for the selected script version you read, so an edit never overwrites one you did not see.
  • Ids from another team, another sequence or a deleted row are reported as not found.

Reads, pages and limits

  • Start with list_sequences, then get_sequence (summary and counts) or get_sequence_status (failures, cheaper).
  • list_scenes and list_shots page with limit and the opaque nextCursor. Prompts and media are opt-in (includePrompts, includeAssets).
  • get_production_bible returns style, cast, locations, elements and every scene's narrative in one call. When it cannot include everything, a *Truncated field names the list tool (and cursor) that continues it.
  • A successful result has structured content and the same JSON as text; a coded error carries structuredContent.error (code, message, details). A read over 256 KiB is refused with a message telling you to page; nothing is silently cut. A write whose result cannot be returned answers RESULT_NOT_RETURNED with the ids it did write: the work is done, so do not call it again; read the entity back.

Resources

For hosts that attach context rather than call tools:

  • openstory://sequences/{sequenceId}/summary — same as get_sequence.
  • openstory://sequences/{sequenceId}/bible — same as get_production_bible.
  • openstory://sequences/{sequenceId}/scenes/{sceneId} — same as get_scene.

resources/templates/list returns the templates, resources/list the summary and bible of your 50 latest sequences. Every resource has a tool, so nothing depends on a host supporting resources.

Inline views (MCP Apps)

get_sequence links an MCP Apps view (ui://openstory/sequence-card.html): hosts that render MCP Apps show the poster, status, counts and music inline, with a link that opens the sequence in the app. While the run is processing the card updates itself and, when it ends, posts a message into the chat so the agent picks up without polling. Hosts without MCP Apps get the plain structured result; the view changes nothing in it.

The production workflow

This is the loop an agent should follow. It is also the procedure to put in an agent skill.

  1. Create (optional). create_sequence with a script starts a new sequence (same input as POST /api/v1/sequences); poll get_sequence_status until the storyboard is done. regenerate_storyboard replaces a sequence's whole script, style or aspect ratio and rebuilds every scene, so ask first.
  2. Inspect. get_sequence_status for what is ready and what failed. get_production_bible for the story and cast. get_scene for the scene you will touch — note script.id.
  3. Edit. update_scene with sequenceId, sceneId, expectedScriptVersionId (the script.id you read) and only the fields you change (scriptExtract, title, location, timeOfDay, storyBeat, continuity). A CONFLICT means the scene changed since you read it — possibly your own edit whose reply was lost (details.selectedScriptVersionId is the current version): read it again and redo the edit only if it is still needed. changed: false means nothing differed. Structure: create_scene (optionally with its script), reorder_scenes, delete_scene / restore_scene, create_shot, reorder_shots, delete_shot / restore_shot, update_shot (length, start frame on/off), update_sequence (title, target length, music on/off, default video model). Deletes are soft and undoable: list_deleted and list_archived_sequences show what can be restored. None of these starts generation. Shot content: get_shot_spec / update_shot_spec (rebuilds the prompts from the spec, free), update_shot_prompt (a hand-written prompt is kept by later rebuilds), restore_shot_prompt_version, rebuild_shot_prompts (free unless the spec is stale, then an AI rewrite that spends credits), list_shot_dialogue / update_shot_dialogue / select_shot_dialogue_version, select_shot_dialogue_reading / discard_shot_dialogue_reading, select_shot_image_version, select_shot_video_version (ids from list_versions). Cast and music: create_character / update_character / delete_character / restore_character, set_character_voice_enabled, list_character_voices / select_character_voice_version, select_character_sheet_version and discard_ / undiscard_character_sheet_version; the same for locations (create_location … select_location_sheet_version); set_element_description, rename_element_token, delete_element / restore_element; update_music_prompt, restore_music_prompt_version, select_music_track, discard_music_track / undiscard_music_track. list_deleted_cast shows what can be restored. None of these starts generation. One shot, one sheet, one track (each spends credits, so confirm with the user first — these do not go through a plan): generate_shot_image, generate_shot_image_variants → get_shot_variant_grid → select_shot_image_variant, generate_shot_video (cancel_video_render stops waiting on one), render_shot_at_quality / render_sequence_drafts_at_quality for Ark drafts, add_model_to_sequence then select_sequence_model, regenerate_character_sheet / regenerate_location_sheet, recast_character / recast_location, generate_character_voice (the first take becomes the voice; cancel_character_voice), generate_music, rewrite_music_prompt, regenerate_shot_dialogue (list_shot_dialogue_claims / cancel_shot_dialogue), cancel_pending_shot_artifact. Model ids come from list_models. Each returns when the run starts; read the shot, cast or music again to see the result. Files: when the user attaches an image, clip or audio file, upload_media with its url or base64 data (8 MB) and use, then attach the returned upload with set_shot_image_from_upload, set_shot_video_from_upload, set_music_from_upload, set_character_sheet_from_upload, set_location_sheet_from_upload, add_element or replace_element. An image of a real person is refused (ATTESTATION_REQUIRED) until the user confirms they hold the rights: ask them, then upload again with portraitAttestation.
  4. See the effect. The edit returns the scene id, its new script version id, its shot ids and its first shots' staleness; get_scene reads the scene back. list_shot_staleness pages the rest. Editing starts no generation.
  5. Plan. plan_generation with mode: "stale" and a depth (prompts, images, dialogue, video or music: how far to update what the edit made stale; whole sequence, sceneIds or shotIds) or mode: "missing" with stopAt (continue an unfinished sequence, whole sequence only), or retry_failed_work after failures. A plan starts nothing. It returns per-stage shot ids, skipped shots, models, an estimate in USD (null when a component has no price) and blockers.
  6. Ask for approval. Show the user the concrete work and the cost from the plan. Do not execute without a yes.
  7. Execute. execute_generation with planToken and confirm: true. If the work or price moved since planning it is refused (CONFLICT with details.code: "PLAN_CHANGED": plan again). A live run, a blocker or too few credits also refuse it, and the plan stays executable. Safe to retry after a timeout: a repeat returns the same workflowRunIds and never charges twice; PLAN_CHANGED or GENERATION_IN_PROGRESS on a repeat means the earlier call started the work — check get_sequence_status.
  8. Poll. get_operation_status with the workflowRunIds, every pollAfterSeconds. It reports those runs only: per-shot failures, skips, and a terminal flag. partially_failed lists what failed; plan a retry for it. LAUNCH_INCOMPLETE from execute_generation names the runs that did start: poll them and do not plan the same work again.
  9. Inspect and export. Read the shots again. plan_export previews what an export would do; start_export renders an MP4 (no plan or confirm: it spends no credits, and a ready MP4 of the same cut is reused). Poll get_export_status. The MP4 renderer runs in production only: on previews, local dev and self-hosted Deploy-button installs an export does not render.

The Studio

The Studio makes standalone images and videos, outside any sequence. Each generation spends credits, so confirm with the user first.

  1. Choose a model. list_models: an entry with studio set is offered in the Studio, and studio says what it takes (video modes, durations, reference limits, end frame, audio).
  2. Attach files (optional). upload_media with use: "studio" and no sequenceId. Pass the returned upload as a reference, start or end frame. Earlier uploads are in list_studio_uploads, earlier results in list_generated_assets (source: "studio"); their URLs are accepted too. A real person needs portraitAttestation, as above.
  3. Write the prompt (optional). draft_studio_prompt writes one from the references (uses credits).
  4. Generate. create_studio_assets with activity (image or video), the model, aspectRatio and the prompt; a video also takes duration and mode (text, reference or frames). Up to 4 at once.
  5. Poll. get_generated_asset with each returned id until status is completed or failed.
  6. Refine. edit_studio_asset rewrites a finished Seedance clip from a prompt; get_studio_edit_history shows the chain. render_studio_asset_at_quality turns a finished draft into its 1080p final. set_studio_asset_favorite marks keepers; delete_studio_asset removes one for good (confirm first).

Not available through MCP yet

  • Choosing another designed voice take or assigning a library voice (both write ElevenLabs' account-wide voice slots).
  • Publishing to social platforms.
  • Cancelling a running operation from execute_generation (single renders and claims can be cancelled; see above).

Client compatibility

ClientStatus
Official MCP SDK client 2.1.0 (versionNegotiation: auto)Tested in CI against the server handler (auth stubbed; the HTTP auth layer has its own tests): discovery, reads, sequence-wide and scene-filtered shot paging, scene ↔ shot navigation, resources, missing scope.
Clients that only speak the 2025 handshakeServed with no session; each request independent, tools and resources alike (tested with the official SDK client in legacy mode).
Claude CodeUntested against a deployment.
Claude.ai custom connectorUntested.
CursorUntested.
Codex, ChatGPTUntested.

"Untested" means no one has connected that client to a deployment and run the workflow above. Results are recorded here as they are verified.