Skip to main content
Cue speaks MCP, so any MCP-capable coding agent or client, from Cursor, Windsurf, VS Code, Zed, Claude Code and Claude Desktop to one you wrote yourself, can run all five workflows by calling tools instead of writing HTTP. It is the same API underneath. Every tool authenticates with your Cue key, obeys the same rate limit and spend cap, and bills exactly like the REST call it wraps.

Endpoint

https://cue.vibeset.ai/mcp

Transport

Streamable HTTP (stateless)

Auth

API key, either header

Authentication

Cue’s MCP server uses your API key, not an OAuth flow. Both headers work, so a config written against either one is fine:
The gateway checks neither. It defers /mcp to the app, which reads the bearer token first and falls back to x-api-key. Bearer is what the MCP authorization spec uses and what most clients default to. Only tools/call is authenticated. initialize, tools/list and ping stay anonymous, because a client has to finish a handshake before it has anywhere to put a credential.
Many remote MCP servers use OAuth and prompt you to sign in. Cue is simpler. Paste a key from the developer portal into your client’s headers, the same key you would use for REST.
Tools bill exactly like the REST calls they wrap, so an agent left running against real videos spends real money. Accounts are provisioned with a $100 monthly spend cap. Check yours in the portal before you point an agent at a catalog. Keep keys server-side, and treat an agent config holding one like any other secret.

Connect

Most MCP clients take the same config shape, a streamable-HTTP server with a header. Add this cue block to your client’s MCP config:
That drops into most agents as-is: .mcp.json (Claude Code), .cursor/mcp.json (Cursor), VS Code’s mcp.json, ~/.codeium/windsurf/mcp_config.json (Windsurf), Zed’s settings, and others. Some clients omit "type": "http" and take url plus headers alone. A few client-specific notes:
Agents with an mcp add command register Cue in one line, for example Claude Code:
Cursor, Windsurf, and others accept the JSON block above in their MCP settings instead.

What you can do

Eight tools. Five are the workflows, three are plumbing. See the Tools reference for parameters, examples, and responses.

Two decisions belong to the person

By default an agent using Cue stops twice and asks, rather than running the whole job on its own:
  1. Which track. match_music returns a shortlist, not an answer. The agent should show several with their title, artist, tempo, length and whether they are instrumental, and let you pick.
  2. Where it loops or cuts. loop_music returns a plan and plan.alternates, other loop regions found in the same track. get_sync_points returns a ranked list of offsets. Either way the agent should show the options and let you choose.
This is deliberate. Whether a track suits your piece turns on taste, tone and brand, and none of that is in any field this API returns, so it is not the agent’s call to settle quietly. To skip the check-ins, say so: “just pick the best one and run it through.” The agent will go end to end and tell you afterwards what it chose and why. That is a mode you opt into.
Ask an agent to rank tracks by score, seam_score or plan_score and it will produce a confident, wrong ordering. Each of those ranks within a single response. seam_score is scaled per song and plan_score per candidate set, so a 1.000 on one track is not better than a 0.91 on another. Compare on the plain facts instead: repeats, the loop region length, sections_covered and end_label.

A typical agent flow

Ask your agent something like “Find music for this clip and loop the best track under the whole thing.” Under the hood it chains the tools:
1

Ingest

ingest_video(video_url=…) returns a video_id and a poll of /v1/videos/{video_id}. Then check_video_status(video_id) until it reads ready. Already have a video_id? Skip straight to matching.
2

Match

match_music(video_id=…) returns a ranked shortlist, each track with a track_id.
3

Sync or loop

get_sync_points(video_id, track_id) for where to cut, or loop_music(video_id, track_id) to lay a short track under a long clip.
underscore sits outside that chain. It takes a script and nothing else, so an agent working from a draft, a storyboard, a voiceover, a post, can call it before any footage exists. map_beats sits outside it too. It takes audio you can serve and never touches a video. If the agent already knows which track it wants, track_id is the Soundstripe song id. Pass it straight to get_sync_points or loop_music and skip matching entirely.

The async pattern

First-time work runs off the request path so it never times out, and comes back as a job instead of a result:
Job ids are bare UUIDs. Beats job ids are 32 hex characters with no dashes. Neither carries a prefix. Have the agent call check_job(job_id, kind), where kind is "match", "loop", "align" or "beats", until it returns status: "complete". Warm calls return the result directly with no polling.
match_music(video_url=…) is the exception. It ingests only. It returns a video_id, a poll of /v1/videos/{video_id}, and a next telling you to call match again once the video is ready. There is no job_id in that response, so check_job has nothing to poll. Use check_video_status(video_id), then call match_music(video_id=…).
Expect a job on the first call for any given pairing. loop_music is warm only when that exact (video_id, track_id) pair has been built before. get_sync_points is warm on that, or when Cue already holds both the video’s sync profile and the track’s audio profile. match_music returns a job whenever the video’s analysis is not cached yet.

Errors

Auth failures are answered at the HTTP layer, so a client can check the status code. A rejected tool call is a real HTTP 401 with a WWW-Authenticate: Bearer challenge. Every other failure comes back as an MCP tool error carrying the same reason the REST API would give, because a bare transport status reaches an agent as an opaque failure it cannot act on:
  • 401, missing or invalid key. An HTTP status, not a tool error. Check the header in your client config.
  • 402, no card on file, credits spent or expired, or the monthly spend cap hit. The message says which.
  • 403, the video or job belongs to another account.
  • 404, unknown video_id or job_id.
  • 429, over the 60 requests per minute limit. The agent should back off and retry.
  • 502, an async match, loop or sync job failed. A failed beats job comes back as a 422.
See Errors & rate limits for the full list.

Billing

The MCP server is a thin facade over /v1. A tool call is authenticated, rate-limited, spend-capped, and metered exactly once, identically to the REST call it wraps. Going through MCP never bills twice. check_video_status is never billed. check_job is not billed either. The underlying job bills once when it completes, whether or not you ever poll it, so polling adds nothing and skipping the poll saves nothing. What each call costs is on Metering & pricing.

Troubleshooting

Confirm the server is connected in your client’s MCP status (/mcp in Claude Code, the MCP or tools panel in an IDE or desktop app) and reload or restart it after editing the config. Check the JSON is valid and the URL is exactly https://cue.vibeset.ai/mcp.
Your key is not reaching the server, or it is no longer valid. Both Authorization: Bearer and x-api-key are accepted, so the header name is not the problem. Check the key is active in the portal, since a revoked key stops working immediately, and that your client is sending headers on the POST rather than only on the initial handshake.
That is the async pattern. Cold work returns a job_id. The agent should call check_job(job_id, kind) until status is complete. If it stays processing for minutes, the video may still be analyzing, so check check_video_status first. Do not resend the original call while a job is in flight — poll the job you have. Resending with the same idempotency_key is safe if you must: it returns the original job rather than starting a second one.
The video is not meaningfully longer than the track, so there is nothing to loop, and you are not billed. hint says why in plain language and retryable says whether calling again can change it. Use loop for clips longer than the track by 30s or more. For shorter clips, use get_sync_points and play the track straight. See Loop.