The MCP server runs inside the API process and uses the product services shared with the app. Where a tool has a REST equivalent, both use the same validation, lifecycle and errors. Explore and some presentation tools have no REST equivalent. MCP lets the workflow become something an agent can complete on its own: find the footage, read what is in it, propose an edit, take a correction, and render.
Connecting#
Connect from your AI app
- Sign in to Anjin and open the account menu → Connect your AI, or go directly to Connect your AI.
- Choose your AI app and follow its connection steps. Approve the connection in Anjin when prompted.
- Once connected, ask your AI app: List my footage in Anjin. Check that it lists the recordings you expect before asking for an edit.
Choose the recording you want to use, say who the edit is for and what question it should answer, then ask to see the plan before rendering. Connecting an AI app does not choose or approve footage for your campaign.
The connection page guides compatible clients through OAuth approval. OAuth grants read and write access, never admin. If you need a key-based connection, choose Use an API key instead; creating a key still requires the relevant account permission.
Manual connection with an API key
The MCP endpoint is:
https://anjin-media-api.fly.dev/mcp
API-key clients send the same mk_live_ key the REST API takes in an Authorization header on every request. To manage keys, open the account menu → Developers → API & MCP. See Authentication for scopes and key handling. The examples below use an API key.
Claude Code
claude mcp add --transport http anjin-media \ https://anjin-media-api.fly.dev/mcp \ --header "Authorization: Bearer $ANJIN_API_KEY"
Add --scope project to write it into the repository's .mcp.json instead of your own machine's configuration.
Any streamable-HTTP client
Anything that speaks streamable HTTP and can set a header will connect. The shape most clients take:
{
"mcpServers": {
"anjin-media": {
"type": "http",
"url": "https://anjin-media-api.fly.dev/mcp",
"headers": {
"Authorization": "Bearer ${ANJIN_API_KEY}"
}
}
}
}Read the key from the environment where your client supports it, as above. A key pasted into a configuration file is a key that ends up in version control, and the only remedy for that is revoking it.
Checking it works
No client needed. A single request lists the tools, which is also the cheapest proof that your key resolves and carries at least one scope:
curl -sS https://anjin-media-api.fly.dev/mcp \
-H "authorization: Bearer $ANJIN_API_KEY" \
-H "content-type: application/json" \
-H "accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'event: message
data: {"result":{"tools":[{"name":"list_source_groups","description":"List the source groups …The 55 tools#
Grouped by what you would use them for. The scope column is the scope that tool requires. Workspace availability, readiness and account entitlements can also determine whether a call may run. Use tools/listfor the current input schemas and descriptions.
Sources and transcripts
| Tool | Scope | What it does |
|---|---|---|
list_source_groups | read | Browse the footage library, with durations and camera counts. |
get_source_group | read | Read a source group's files, metadata and readiness. |
get_transcript | read | Read diarized speech with segment or word-level timing. |
get_speakers | read | Read speaker labels, speaking time and sample quotes. |
name_speakers | write | Give diarized speakers their display names. |
mask_transcript_words | write | Mask or restore words or terms for subsequent reads and renders; existing output can become stale. |
set_content_regions | write | Override source intro, outro, sponsor and call-to-action regions used to exclude material from edits. |
redetect_content_regions | write | Re-run source content-region detection, replacing a manual override. |
Explore ideas
| Tool | Scope | What it does |
|---|---|---|
get_workspace | read | Read workspace capabilities and whether Explore is available. |
explore_source | write | Generate transcript-grounded editorial concepts from a source. |
get_concepts | read | Read the concepts and current Explore session. |
present_concepts | read | Present concept cards for a person to review. |
select_concept | write | Select a concept within the current Explore session. |
refine_concept | write | Refine the selected concept with editorial feedback. |
create_concept_cut | write | Save a selected concept as a cut with explicit output settings. |
Plan cuts
| Tool | Scope | What it does |
|---|---|---|
create_cut | write | Start planning from a brief, one to four source groups and output settings. |
get_cut_plan | read | Read the versioned segment plan; use cut status to check whether a revision has finished. |
list_cuts | read | Browse the account's cuts. |
get_cut | read | Read one cut's status and settings. |
revise_cut_plan | write | Replan with a plain-language editorial instruction. |
update_cut_plan | write | Remove, reorder, pin or unpin plan segments. |
validate_selections | read | Check proposed source selections before using them. |
Render and review
| Tool | Scope | What it does |
|---|---|---|
quote_render_cut | read | Check the export-minute quote before rendering. |
render_cut | write | Render a plan version in one to three aspects; exports consume export minutes. |
get_renders | read | Read a cut's render statuses, signed downloads and sidecars. |
list_renders | read | Browse rendered outputs. |
get_render | read | Read one render and its output metadata. |
present_render | read | Present a finished render for review. |
get_operation | read | Look up the outcome of an earlier call by its operation key. |
delete_render | admin | Remove a rendered output. |
Brand, profiles and visual plans
| Tool | Scope | What it does |
|---|---|---|
list_brand_kits | read | Browse reusable brand colours, typography and assets. |
save_brand_kit_defaults | admin | Change a brand kit's defaults for future work. |
list_render_profiles | read | Browse reusable render profiles. |
get_render_profile | read | Read a render profile and its versions. |
create_render_profile | write | Create a reusable render profile. |
save_render_profile_version | write | Append a new version of a render profile. |
archive_render_profile | admin | Archive a render profile. |
get_visual_plan | read | Read the framing plan associated with a cut. |
propose_visual_plan | write | Propose an optional visual plan for review. |
get_visual_plan_proposal | read | Read a proposed visual plan. |
preflight_visual_plan | read | Check a visual proposal before approval. |
approve_visual_plan | write | Approve a visual proposal for the cut. |
Thumbnails
| Tool | Scope | What it does |
|---|---|---|
get_thumbnail_contract | read | Read supported thumbnail layouts and controls. |
get_thumbnail_style | read | Read a brand kit's thumbnail defaults. |
save_thumbnail_style | admin | Change thumbnail defaults for a brand kit. |
preview_thumbnail_style | read | Preview a thumbnail style before saving it. |
create_thumbnail | write | Start a thumbnail draft for a finished render. |
get_thumbnail | read | Read a thumbnail draft and its current state. |
get_output_thumbnail | read | Read the thumbnail associated with an output. |
save_thumbnail | write | Save edits to a thumbnail draft. |
preview_thumbnail | read | Preview the draft thumbnail. |
approve_thumbnail | write | Explicitly approve a thumbnail for use. |
retry_thumbnail | write | Retry thumbnail preparation. |
export_thumbnail | read | Download an approved thumbnail export. |
inspect_thumbnail_candidate | read | Inspect a source-frame candidate for the thumbnail. |
Creating a cut
Two refusals come from the service rather than the tool schema, so a call that looks valid to a client is still rejected:
Reading a cut's readiness
Readiness is status, never plan !== null: plan is null only before a cut's first plan is committed, and a cut revising back through planning hands back the previous version, fully populated. Wait on status reaching planned, or on a bumped plan_version - waiting on a non-null plan reads a revision as finished the instant it starts. The tool descriptions state this contract themselves.
The loop#
Four tools in order, and the order is the product. It is written into the tool descriptions themselves so an agent that never reads this page still follows it:
- 1
create_cut
The brief and one to fourreadysource groups. This returns an id andplanning, not a plan - the planner is still reading the transcripts. - 2
get_cut_plan
Poll untilstatusreadsplanned, then read every entry against the brief. This is the review step the whole loop exists for: the plan names the seconds of footage the edit is made of, and it is far quicker to correct than a finished file. - 3
revise_cut_plan - or update_cut_plan
Say what is wrong in a sentence and the planner writes a new version. For an edit you already know exactly - drop entry 4, swap 2 and 3 -update_cut_planapplies it directly with no planner call at all. Re-read the plan after either. - 4
render_cut
Only once the plan says what you want it to say. One to three aspects, one render each, and the files arrive throughget_renders.
The loop can run as many times as it takes. Every version is kept, and render_cut takes an explicit plan_version, so an agent can render the version a person approved rather than whatever is latest.
Scopes#
The connection is authenticated once and then each tool checks its own scope. That is a deliberate difference from the REST routes, where one scope gates the whole request: a single MCP connection multiplexes tools with different requirements, so gating the mount would stop a read-only key from doing the reads it is entitled to.
- 31 tools require
read. Read sources, plans, profiles, outputs and previews. - 20 require
write. Create or revise work, prepare media, mask words and approve per-cut proposals. - 4 require
admin.delete_render,archive_render_profile,save_brand_kit_defaultsandsave_thumbnail_style.
tools/list exposes the catalogue; permissions are checked again when a tool is called. OAuth connections never receive admin scope. Use the app to manage renders and brand defaults, or an explicitly admin-scoped API key for operations such as archiving a profile.
Transport#
Streamable HTTP, and stateless. No session is established, no mcp-session-id is ever issued, and there is nothing to keep alive or resume: every request carries its own bearer key, which already identifies the caller. A client that reconnects has lost nothing, because there was nothing to lose.
One request per call
JSON-RPC batch arrays are refused. Send this:
[
{"jsonrpc":"2.0","id":1,"method":"tools/list"},
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
]and the whole POST is rejected before the transport sees it:
{
"jsonrpc": "2.0",
"id": null,
"error": {
"code": -32600,
"message": "Batch requests are not supported - send one request per call (rate limiting is per-request)"
}
}The reason is the rate limiter: one HTTP request spends one token, and a batch would have driven any number of tool calls on that single token. Rejecting it keeps the published limits honest. Send one request per call and let your client pipeline them.
Rate limits
The same two buckets as REST apply here, counted per HTTP request: 60 a minute for the key and 120 a minute for the account across every key it holds. Every response - tool results and transport errors alike - carries x-ratelimit-remaining, and a refusal carries retry-after in whole seconds. An agent in a tight polling loop is the caller most likely to meet them: Errors & limits covers backing off properly, and the concurrency ceilings that sit behind them.
Errors#
A failing tool returns the same RFC 9457 problem document the REST API would have returned, serialised into the text content of a tool result with isError: true. Nothing is reworded on the way through - one code path builds both:
{
"type": "https://anjin.media/errors/insufficient_scope",
"title": "Insufficient Scope",
"status": 403,
"detail": "this API key lacks the required \"write\" scope",
"code": "insufficient_scope"
}So an agent can branch on code exactly as your backend would: 422 group_not_ready means wait for ingest, 409 busy means a planner run is already in flight, 404 not_found means the id is not yours.
{
"type": "https://anjin.media/errors/invalid_api_key",
"title": "Invalid Api Key",
"status": 401,
"detail": "Invalid API key",
"code": "invalid_api_key"
}Briefing an agent#
The tools are shaped so that plain instructions work. Three that do the right thing:
- Read first, then brief. “Look at my source groups, find the customer panel from last month, and read its transcript. Then draft a 60 second cut answering the data residency objection - open on the question as the audience puts it. Show me the plan before you render anything.”
- Argue with the plan. “That plan opens too slowly. Revise it to lead with the number, keep the second and fourth segments exactly as they are, and drop the pricing tangent. Then show me the new version.”
- Render, deliberately. “The plan is right. Render version 3 in 9:16 and 1:1, then give me the download links and tell me whether QC passed on both.”
The pattern in all three: read the footage before briefing against it, argue with the plan rather than the output, and make rendering an explicit instruction. An agent that renders first discovers what it got wrong in the most expensive place.
From here: Creating a composition documents every setting create_cut accepts, Composition results documents the plan document and the render sidecars an agent reads back, Working with an archive covers pointing an agent at a library rather than a single recording, and the MCP overview makes the case for the whole approach in fewer words.
