AI Assistants & Automation

MCP Tool Reference

Everything needed to build a client against the StudioCut.Video MCP server — the endpoint, the twelve tools, the order they have to be called in, the two kinds of error, and every limit you will meet.

This is the reference for building a client. If you just want to connect Claude Desktop and get on with it, read Connect Your AI Assistant instead — it takes about five minutes. It is one of the guides in the StudioCut.Video Help Center.

The endpoint

POST https://app.studiocut.video/mcp
Authorization: Bearer <your 40-character key>
Content-Type: application/json

One endpoint, JSON-RPC 2.0 over streamable HTTP, implementing MCP revision 2025-11-25 (and accepting 2025-06-18). POST only — there is no GET, no DELETE, and no SSE session flow.

The endpoint is stateless. Every request re-authenticates; no cookie or session id is kept. There is no session to establish, resume or tear down, and the bearer token is required on every request including initialize.

Authentication

Only a key generated on the MCP and API Keys page works. A general-purpose Odoo backend key — including a wildcard key with no scope — is refused. Do not tell your users to paste one from the backend's "New API Key" button; it will always fail.

An authentication failure returns HTTP 401, a WWW-Authenticate: Bearer challenge, and an empty body — no JSON-RPC envelope. The response is byte-identical for an unknown key, an expired key, a wrong-scope key and a valid key whose account was archived. That is deliberate: telling them apart would tell an attacker which keys exist. Your client cannot distinguish them either, so surface one actionable message — "StudioCut rejected the key — generate a new one on the MCP and API Keys page."

Keys live at most 30 days, so handle expiry as a normal event, not an exception. Rotation is the supported way to replace one without downtime: the predecessor keeps authenticating for a grace period (24 hours by default) while you update your configuration.

Protocol methods

Four methods are dispatched. Anything else returns -32601 Method not found.

MethodNotes
initializeEchoes your protocolVersion when it is a supported revision, otherwise replies with 2025-11-25 and leaves the decision to you
pingReturns an empty result object
notifications/initializedAny request without an id is a notification. It gets HTTP 202 and a zero-length body — a client that waits for a response body here will hang
tools/listReturns {"tools":[{"name","description","inputSchema"}, …]}. No pagination — nextCursor is never emitted, so do not write a paging loop
tools/callarguments is optional and defaults to {}

Two things about tools/list that shape a client:

  • Schemas are resolved per request, so enum values track the live catalogue. If a visual style is retired it disappears from the relevant enum with no deploy and no version bump. Read the schemas rather than hard-coding the vocabulary, and expect enum contents — not shapes — to drift between sessions.
  • listChanged is false. An administrator can disable a tool, but no notification is sent, so re-read the list at the start of each session rather than caching it indefinitely.

Results arrive as MCP content blocks with the tool's return value JSON-encoded inside a text block. Parse content[0].text as JSON to get the documented result object; every tool returns an object, never a bare array or scalar.

The twelve tools

tools/list returns thirteen entries — these twelve plus a server_info tool from the transport layer.

ToolWritesSpendsPurpose
list_design_options——The accepted design vocabulary, and the character caps on custom free-text fields
list_productions——The account's productions, filterable
get_production——One production's settings
get_production_status——Where it is and what to do next — the polling tool
get_storyboard——The Phase-1 plan, so a human can approve on evidence
get_balance——What this credential can spend, its cap and the spend so far
get_download_url——A link to the finished video
validate_productions——Dry run; creates nothing
create_productions✓—Creates drafts, free
estimate_cost——Price, breakdown and a confirm_token
start_production✓✓Starts the pipeline — the only tool that spends
approve_phase1✓—Approves the storyboard; the point of no return

Call get_balance before planning a batch. "Insufficient credits" is only actionable if it arrives before your agent has designed twenty videos. It also reports separately any balance that exists but cannot be spent through this channel, so a wallet that looks empty is explained rather than mysterious.

The canonical flow

list_design_options       learn the vocabulary
        ↓
validate_productions      dry run; fix errors; show the user `normalized`
        ↓
create_productions        drafts; free; capture the returned ids
        ↓
estimate_cost             price + confirm_token (5 min TTL)
        ↓
   ── SHOW THE USER THE PRICE, GET AGREEMENT ──
        ↓
start_production          spends credits; needs the confirm_token
        ↓
get_production_status     poll, ≥ 20 s apart, until next_action == "approve_phase1"
        ↓
   ── SHOW THE USER THE STORYBOARD, GET AGREEMENT ──
        ↓
approve_phase1            POINT OF NO RETURN; phases 2-5 run unattended
        ↓
get_production_status     poll, ≥ 20 s apart, until next_action == "none"
        ↓
get_download_url          give the link to the user to open

The two human checkpoints are marked, and both exist because the step after them cannot be undone. Design the integration around that gate rather than against it — it is the main thing standing between a looping agent and a month of someone's credits.

The production payload

validate_productions and create_productions take the same payload, in any of four shapes: an array of objects, a single object, a wrapper object {"productions": [...], "brand_kit": {...}}, or any of those as a JSON string — so a model that emits "here is my JSON" as text works as well as a properly decoded structure.

Only two fields are required per item: name (≤ 500 characters) and input_content (≤ 50 000 characters). Everything else is optional. Human-friendly keys are aliased — video_title, title and name are the same field, and keys are matched case-insensitively with whitespace and hyphens collapsed to underscores.

Two behaviours worth building around:

  • Off-list values are redirected, not rejected. An unrecognised visual style becomes a custom style rather than failing the item. In validate_productions, errors invalidate an item but warnings never do — the offending field is dropped and the server default applies.
  • normalized is exactly what would be created. Show it to the user when confirming a batch; it is the record after aliasing, brand-kit folding and coercion.

Extra languages are separate charged videos. A production with five extra languages costs six videos, not one. The ceiling is five extra languages, counted after de-duplication, and a longer list is refused outright — named, not silently truncated — in the dry run as well as at create time.

The error model

Getting this distinction right is the single most important part of the integration.

Protocol errors — fix the client

CodeMeaning
-32700Parse error — the body was not valid JSON
-32600Invalid request — not a JSON-RPC object, or a batch array, which this revision does not support
-32601Method not found
-32602Invalid params, or an unknown/disabled tool name — re-read tools/list
-32603Internal error — retry once with backoff, then report

Tool errors — hand them to the model

A tool that runs and fails is a JSON-RPC success carrying isError: true. Everything after "does this tool exist" is a tool error: a bad argument, a rate limit, a spend cap, an insufficient balance, a production in the wrong state.

The message text is written to be read by a language model and acted on — it names the current state, the shortfall, or the next call to make. Pass it through verbatim. Do not replace it with a generic "tool failed", and do not treat it as a transport fault and tear down the session.

Tool errors also carry a machine-readable reason_code and a details object alongside the same sentence, so an agent branching on insufficient credits versus storage full is not pattern-matching English. Branch on the code where your agent should behave differently — retrying a rate limit makes sense, retrying an insufficient balance does not.

Transport codes: auth_failed, unknown_tool, tool_unavailable, rate_limited, cap_exceeded, schema_unavailable, invalid_arguments, user_error, internal_error.

Domain codes, with what details carries:

Codedetails
insufficient_creditsWhat is required, what is spendable, and a top-up link
key_credit_cap_reachedThe cap, the period, the spend so far, this call's quote, and the reset time
storage_fullUsed, limit, needed, freeable, and how many productions could be purged
concurrency_heldThe limit, the count, and references of the productions holding the slots
quote_requiredNo usable quote — call estimate_cost
quote_expiredThe token is past its five-minute life
quote_staleThe token is valid but the price moved — re-quote and re-ask the user
already_startedThe reference, state and working state
production_expiredThe files were auto-purged; when, and what remains
plan_entitlement_requiredThe entitlement the account lacks
content_rejectedThe production was refused by the content policy
voice_language_incompatibleThe voice and language that do not go together
too_many_languagesWhat was requested against the five-language ceiling
queuedThe start was accepted but is waiting behind other work — this is success, not failure

Treat an unrecognised reason_code as an internal error and fall back to the sentence. The list grows; the sentence is always there.

HTTP-level responses

StatusMeaning
200A JSON-RPC response — success, tool error, or protocol error
202Notification acknowledged, empty body
401Authentication failed, empty body
413Request body over 1 MB

Limits and caps

LimitDefaultScope
Rate limit120 calls/hourPer credential per tool, rolling hour
Concurrent productions1Per credential
Credit capSet per key; unset means no per-key ceilingPer credential, per day/week/month
Items per validate/create call20Per call
Payload characters2 097 152Per validate/create call
HTTP body1 MBPer request
Extra languages5Per production
Quote lifetime300 sPer confirm_token
Key lifetime≤ 30 daysPer key

Notes that matter when you build against these:

  • The rate limit is per tool, not global. 120 status polls do not consume your listing budget. It is a rolling hour, and the error tells you exactly how many seconds to wait — honour that number.
  • The credit cap is evaluated inclusive of the call in front of it. A start whose own quote would breach the cap is refused before any money moves, rather than admitted and discovered afterwards. get_balance reports the cap, the period and the spend so far, so an agent can pace itself.
  • Concurrency of 1 means one production at a time per key, and a storyboard awaiting approval holds the slot. That is intended pressure: the alternative is an agent starting a second paid production while the first is still unreviewed.
  • Administrators can change the rate limit and the caps, and can disable any tool. Treat every number here as a default you discover at runtime, and treat a missing tool in tools/list as normal rather than a fault.

Pricing through this channel

Assistant-driven production spends the account's plan credits and purchased credits. Drafting, validating, quoting, polling, reading a storyboard and fetching a download link are all free; the single charge lands when the production starts.

Two things differ from the web app, and both are in the customer's favour to know up front:

  • Plan feature gates do not apply to work started through this channel, and it does not consume the account's monthly app allowance. Storage limits, content policy, voice/language compatibility and the five-language ceiling still apply on every channel.
  • A 12.5% premium applies when both the quality tier is 2 or higher and the target duration is over 30 seconds. Below either threshold the price is identical to the app.

estimate_cost's breakdown reports channel, surcharge_percent and surcharge_tokens as separate fields, so you can show the user the premium as its own line rather than as an unexplained difference. Show the total before you call start_production — that is the whole point of the confirm token.

Ownership and scoping

Each key is bound at creation to either the user's personal account or one specific agency — never both, and never "whichever the user is currently looking at". The binding is immutable for the life of the key.

Everything follows from that binding: listings, reads, creates and spend all resolve against the bound context. There is no way to switch context at call time and no argument that widens scope. A user who needs both creates two keys.

For your integration this means a key is a complete, self-describing context. You never need to send an account or agency identifier, and you should not build UI implying the user can switch. If a key stops working after an agency membership changes, the correct fix is a new key.

Retention — output may be auto-purged

A customer can turn on automatic deletion of the output produced through this API, after a retention window they choose. It affects what your client can promise:

  • the clock starts when a video download link is issued — nothing else starts it, and a thumbnail preview does not;
  • output that was never downloaded is never auto-deleted;
  • deletion removes the files, not the record — get_production keeps working and reports the production as expired, with its name, cost and dates intact, while get_download_url returns production_expired rather than pretending the video is still coming;
  • the customer can mark any production Keep to exempt it, and can change or switch off the window at any time.

Treat production_expired as final. If a user may want a video later, tell them to download it or mark it Keep.

Not available today

  • OAuth 2.1. Authentication is bearer API keys only. Nothing to migrate yet, but expect the key path to be the layer OAuth issues into.
  • Publishing. No tool pushes to YouTube or any other platform.
  • Brand-preset creation. A brand_kit styles a batch; it does not save a reusable preset.
  • Server-initiated notifications. No resources, prompts, sampling or progress notifications. Tools only.
  • Batch JSON-RPC. One request object per HTTP request.
  • A REST API. MCP is the only programmatic route today.

Troubleshooting

SymptomCauseFix
HTTP 0, empty body, TypeError: Failed to fetch — browser clients onlyThe endpoint sends no CORS headers, so a cross-origin POST is stopped at the preflight and never sent. Nothing appears in any log because nothing arrivedNot a server fault and not fixable by changing the URL or key. Proxy through your own origin, or call the endpoint server-side. The test console ships a CORS shim for this
401, empty bodyKey unknown, expired, wrong kind, or its account archived — indistinguishable by designGenerate a new key on the MCP and API Keys page, or rotate the existing one before it expires. Confirm it did not come from the Odoo backend
-32602 Unknown or archived toolCached tool list; an administrator disabled the toolRe-read tools/list each session
-32600 on an array bodyBatching is not part of this MCP revisionSend one JSON-RPC object per request
Client hangs after notifications/initializedNotifications get 202 with an empty bodyDo not wait for a response body on id-less requests
413Body over 1 MBSplit the batch; the 20-item cap usually keeps you well under
"A valid confirm_token … is required"No token, expired past five minutes, or issued for another production or userCall estimate_cost again, re-confirm with the user, then start
"The cost of this production changed…"The price rose between quote and startRe-quote, show the new figure, get fresh agreement
"has already been started"Duplicate start_productionUse get_production_status; a queued result is already success
Production seems stuck at phase 1It is waiting for storyboard approval — the one human gatenext_action says approve_phase1. A human must decide
Rate-limit errors while pollingThe poll loop is tighter than 30 secondsBack off to 20 seconds or more and honour the retry figure in the error
A download URL 403s from your serverIt is signed for browser access onlyGive the link to the user; never fetch it server-side
Enum values differ between sessionsSchemas are generated live from the catalogueRead inputSchema; do not hard-code the vocabulary

The test console

We publish a standalone browser console that exercises every method and tool on this page against a live server: StudioCut.Video_MCP-Tester. It runs locally with no build step and ships a CORS shim, so it is the fastest way to learn the flow — and the fastest way to reproduce a wire-level problem before you go digging in your own client.

See also