ABCD Studio exposes its full production pipeline — text-to-image, text-to-video, and end-to-end film assembly — as a Model Context Protocol server. Any MCP-aware client (Claude Desktop, ChatGPT custom connectors, OpenAI o-series apps, agentic frameworks) can direct a film from a single conversation.
abcd_token and the generation runs on that user's credits.create_film_projectadd_characterapprove_castswap_character_lookgenerate_scriptset_creative_briefgenerate_scenesapprove_sceneadjust_scene_camerarevise_camera_plananimate_scenesassemble_filmget_project_statuswait_for_filmlist_film_projectsfix_and_resume_filmproduction_actionget_prompt_auditget_requirementsget_generation_optionsmarketing_directorcreate_product_adcreate_ugc_adcreate_food_commercialcreate_brand_storycreate_real_estate_adgenerate_imagegenerate_videopoll_jobsearch_vaultpublish_assetget_characterpublish_film_to_tvenhance_videoremix_videoget_enhance_statusrefine_storyrerender_best_sceneedit_sceneadd_scene_voiceseparate_stemsremix_audioreplace_vocalsrebuild_mixset_session_musiccreate_seriesplan_episodesearchfetchget_project_status7,575production_action70wait_for_film25generate_video15generate_image6Settings → Connect via MCP shows a personal URL that carries a rotating mcp_sess_… session token — the raw key never travels over the wire. Developers: contact hi@abcdstudio.ai or open the SuperAdmin → API Keys panel and mint a scope-limited mcp_live_… key./api/mcp — either the personal ?token=mcp_sess_… URL, or the bare endpoint with an X-ABCD-API-Key header.X-ABCD-Token: mcp_sess_… (or Authorization: Bearer mcp_sess_…) to the bare endpoint instead. claude.ai / Claude Desktop connectors, ChatGPT and Kimi take a URL only, so use the masked personal URL and treat it like a password. The Connect page masks the token (👁 reveals it for 20 s) and ↻ Regenerate token invalidates a leaked one instantly without touching your client setup — just paste the new URL.job_id should be polled with poll_job or (for pipeline steps) get_project_status. Every image_url, video_url, audio / stem / export URL and published link comes back as a public https:// link on the ABCD domain (…/api/vibe/media/<file>) that your AI client can open directly — never a localhost address.mcp_sess_… resolves server-side to your API key (same scopes and per-hour limits) and is the only thing your AI client stores. Tokens can auto-rotate on a schedule you choose (Advanced → Auto-rotate, with a countdown to the next rotation).mcp_sess_ URL and invalidates the old one — paste the new URL into your client. Use it after a rotation, a revoked token, or whenever the pill stays ⚪ after a call."MCP session expired — open Connect via MCP and tap Reconnect" rather than a generic error; if your ABCD sign-in itself expired, the Connect page says so and asks you to sign in first.animate_scenes clips instead of failing them.{
"mcpServers": {
"abcd": {
"url": "https://abcdstudio.ai/api/mcp",
"headers": {
"X-ABCD-API-Key": "mcp_live_YOUR_KEY_HERE"
}
}
}
}# pip install anthropic openai fastmcp
import asyncio, os
from mcp.client.streamable_http import streamablehttp_client
from mcp import ClientSession
BASE = "https://abcdstudio.ai/api/mcp"
HEADERS = {"X-ABCD-API-Key": os.environ["ABCD_MCP_KEY"]}
async def run():
async with streamablehttp_client(BASE, headers=HEADERS) as (r, w, _):
async with ClientSession(r, w) as session:
await session.initialize()
# 1. Pitch a film
res = await session.call_tool("create_film_project", {
"title": "The Last Signal",
"genre": "sci-fi",
"story_concept": ("A radio operator on Mars catches a "
"signal from Earth long after contact "
"was lost."),
"film_length": "short",
"aspect_ratio": "16:9",
"resolution": "1080p",
})
print(res.content[0].text)
asyncio.run(run())# 1. Initialize a session
curl -sN "https://abcdstudio.ai/api/mcp" \
-H 'X-ABCD-API-Key: mcp_live_YOUR_KEY_HERE' \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18",
"capabilities":{},
"clientInfo":{"name":"probe","version":"1"}}}'
# 2. List all tools (save session id from the response header
# "mcp-session-id" and pass it as Mcp-Session-Id on subsequent calls)
curl -sN "…/api/mcp" \
-H 'Mcp-Session-Id: SID_FROM_STEP_1' \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'GET /api/mcp-public-toolsagainst this deployment. Returns the same 14-tool catalog an integrator sees after provisioning a key — no signup needed.job_id so you can inspect the async shape without waiting for pixels.| Tool | Parameters | Description |
|---|---|---|
create_film_project | title, genre, story_concept, visual_style?, film_length, aspect_ratio, resolution | Start a new film project (session). Writer plans in the background. |
add_character | session_id, name, description, reference_image_url? | Add a character; if no reference, a portrait is generated first. |
generate_script | session_id, wait? = true | Poll storyboard until the Writer produces the scene list. |
generate_scenes | session_id, scene_ids? | Kick off frame generation for all scenes or a subset. |
approve_scene | session_id, scene_id, approved | Approve or reject an individual scene's current frame. |
animate_scenes | session_id, scene_ids?, model?, duration? | Run Seedance video generation on approved frames. |
assemble_film | session_id, export_format = "mp4", resolution?, wait? | Approve assembly + finalize + optional 600s poll for the final export URL. |
get_project_status | session_id | Full state snapshot: stages, characters, scenes, render progress, export URL. |
generate_image | prompt, aspect?, style?, model?, wait? | One-off image. model: seedream (default, Seedream 4.5, 1 cr) · seedream-5 (Seedream 5.0 Pro, 2 cr) · gpt-image-1 (OpenAI GPT Image 1, 2 cr) · nano-banana (Gemini, 2 cr). |
generate_video | prompt, image_url?, model?, duration?, aspect?, motion? | One-off Seedance clip. |
poll_job | job_id, job_type = "image"|"video" | Poll any async job by id. |
search_vault | query, kind? | Semantic search over characters / environments / scenes. |
publish_asset | asset_id, title, kind | Publish an asset to the user's public showcase. |
get_character | name | Look up a character asset by fuzzy name match. |
Per-key, per-hour, sliding-window. ABCD staff can override any of these live via db.mcp_rate_limits without a restart. A rejected call returns {ok:false, error:'rate limit reached (…/h)'} — back off with exponential jitter.
| Tool | Default cap |
|---|---|
generate_image | 30 / h |
generate_video | 10 / h |
create_film_project | 20 / h |
add_character | 60 / h |
generate_script | 40 / h |
generate_scenes | 60 / h |
approve_scene | 200 / h |
animate_scenes | 20 / h |
assemble_film | 10 / h |
get_project_status | 600 / h |
poll_job | 600 / h |
publish_asset | 20 / h |
search_vault | 200 / h |
get_character | 200 / h |
Every tool call returns a JSON object whose top-level shape is stable:
{
"ok": true,
"session_id": "…", "…": "…",
"rate_limit": { "used": 3, "cap": 20, "remaining": 17 }
}{
"ok": false,
"error": "backend rejected request (400)",
"detail": "credit balance too low",
"error_type": "insufficient_credits", // when applicable
"job_id": "…" // when a partial run exists
}Content-flag envelope — when a Seedance render is refused for safety/copyright reasons, error_type is copyright_filter or safety_filter, and a request_id lets support trace the row in db.content_flags.
Any of these keys work as the model argument on generate_video and animate_scenes. Legacy identifiers auto-alias to the current lineup so old clients keep working:
seedance-2.0-mini (default · 720p · 24 fps · smart duration · sound on)seedance-1.0-proseedance-1.5-proseedance-2.0-fastseedance-2.0seedance-2.5The scripted conversation below shows a user directing Claude through a full film project. Each turn shows exactly what tool was called and a compact excerpt of the response. Runnable — swap the story concept, keep the sequence.
{
"title": "The Last Signal",
"genre": "sci-fi",
"story_concept": "A radio operator on Mars catches a signal from Earth long after contact was lost — she has 90 minutes of oxygen to decide whether to reply.",
"film_length": "short",
"aspect_ratio": "16:9",
"resolution": "1080p"
}{
"ok": true,
"session_id": "d94a…c1e2",
"project_id": "d94a…c1e2",
"title": "The Last Signal",
"status": "planning",
"pipeline_stage": "script"
}{
"session_id": "d94a…c1e2",
"name": "Commander Riya Vale",
"description": "South-Asian woman, mid-30s, close-cropped black hair, weather-worn navy Mars-Corps flightsuit with Bengali unit patch, tired but resolute eyes."
}{
"ok": true,
"character_id": "7b3f…9a11",
"name": "Commander Riya Vale",
"portrait_url": "data:image/jpeg;base64,/9j/4AA…",
"description": "South-Asian woman, mid-30s, …",
"session_id": "d94a…c1e2"
}{ "session_id": "d94a…c1e2", "wait": true }{
"ok": true,
"script_id": "d94a…c1e2",
"scene_count": 7,
"scenes": [
{ "id": "s01…", "order": 1, "title": "Silent radio room",
"description": "Cold-open on Riya's static-hissing console...",
"duration": 5 },
{ "id": "s02…", "order": 2, "title": "The signal", "…": "…" },
…
],
"status": "ready"
}{ "session_id": "d94a…c1e2", "scene_ids": ["s02…"] }{
"ok": true,
"session_id": "d94a…c1e2",
"started": [{ "scene_id": "s02…", "order": 2, "status": "queued" }]
}{ "session_id": "d94a…c1e2" }{
"ok": true,
"session_id": "d94a…c1e2",
"pipeline_stage": "frames",
"next_stage": "frames",
"all_approved": false,
"characters": [{ "id": "7b3f…", "name": "Commander Riya Vale", … }],
"scenes": [
{ "id": "s01…", "frame_status": "ready", "approved": false, … },
{ "id": "s02…", "frame_status": "ready", "approved": false, … },
…
],
"render_progress": { "done": 0, "total": 7, "percent": 0 }
}{ "session_id": "d94a…c1e2", "scene_id": "s01…", "approved": true }
{ "session_id": "d94a…c1e2", "scene_id": "s02…", "approved": true }
…{
"ok": true,
"scene_id": "s07…",
"approved": true,
"session_id": "d94a…c1e2",
"pipeline_stage": "assembly"
}{
"session_id": "d94a…c1e2",
"model": "seedance-2.5",
"duration": 6
}{
"ok": true,
"session_id": "d94a…c1e2",
"model": "2.5",
"started": [
{ "scene_id": "s01…", "order": 1, "job_id": "vid_a1…" },
{ "scene_id": "s02…", "order": 2, "job_id": "vid_a2…" },
…
],
"skipped_orders": [],
"errors": []
}{
"session_id": "d94a…c1e2",
"export_format": "mp4",
"wait": true
}{
"ok": true,
"session_id": "d94a…c1e2",
"status": "ready",
"export_url": "https://abcdstudio.ai/api/vibe/media/d94a_export_1080p.mp4",
"duration_secs": 42
}