An MCP server turns an API into a few named tools that an agent calls mid-conversation, and a long job fits behind one when the tool hands back a handle the agent waits on in bounded turns. Paste a podcast link, ask for the gist in French, and no code gets written.
A coding agent can use the VocalStack API the way any program does: read the API reference, write a few lines against the SDK, run them. That is the right path for something that will run without the agent.
MCP (the Model Context Protocol) is for the other case: you are talking to an assistant and want it to transcribe something now, without code. An MCP server publishes a short list of named tools, each with a description and a JSON schema for its arguments. The agent reads the list once and calls a tool by name when a request matches it. "Transcribe this podcast and tell me what they said about pricing" becomes three tool calls, and the agent never sees an HTTP route.
Servers come in two kinds. A local server runs as a subprocess of the client and talks over stdio, which is how most file-system and database servers work. A remote server sits behind a URL and speaks Streamable HTTP, the transport that replaced the older HTTP-plus-SSE (server-sent events) transport in the March 2025 revision of the spec. The client posts each request, and the server answers on the same connection; anything it has to say before the final result is streamed ahead of it. Any client written against the current spec can use either kind.
VocalStack's server is the remote kind, at {{api}}/v1/mcp, and using it costs what the API costs. All you need is the URL.
Open the Dashboard's Developers area at {{app}}/developers. The first tab, Connect an AI agent, shows the server URL and a setup snippet for each client. The three snippets below match the ones shown there.
Run this in your terminal:
claude mcp add --transport http vocalstack {{api}}/v1/mcp
Add this to .cursor/mcp.json in your project, or to ~/.cursor/mcp.json for every project:
{"mcpServers":{"vocalstack":{"url":"{{api}}/v1/mcp"}}}
Any client that supports Streamable HTTP can add the server with this URL. This is the generic form the Dashboard shows:
{
"vocalstack": {
"type": "streamable-http",
"url": "{{api}}/v1/mcp"
}
}
You do not need an API key to start. The first time the client connects, you sign in with your VocalStack account in the browser, and the client keeps the token. The Dashboard puts it in one line: "You'll sign in with your VocalStack account the first time. No key needed."
This is MCP's authorization spec at work, not a VocalStack trick. A remote server answers an unauthenticated request with a 401 that points to its protected-resource metadata: a small JSON document that names the authorization server and the scopes the server requires. The client reads it, runs a standard OAuth 2.1 flow in your browser, and comes back with a token issued for that server alone. Any remote MCP server that follows the spec signs you in the same way, which is why the clients above can do it without knowing anything about VocalStack.
An API key also works, for a client running on a server or in a script. Send it as Authorization: Bearer vs_key_..., the same header the REST API takes. Keys are created under Developers → API keys. The two transcribe tools need the Write and Stream audio permissions. The second sounds odd for a file, but the tools go through the same creation point as every transcription, live ones included, and that point checks both. The reading tools need Read; list_voices needs only a valid credential.
Once you are signed in, the agent sees nine tools. You will rarely name them yourself, but knowing them explains what the agent does next.
Every tool takes and returns camelCase JSON, with the same field names as the /v1 wire types; a transcription's handle is its transcriptionId.
transcribe_url starts transcribing audio or video at a public URL: a file link, a podcast episode, a YouTube video. Live streams are not accepted. Optional arguments: name, spokenLanguage (omit it to detect the language), speakerLabels (auto, on or off) and startS/endS to transcribe only part of the audio.transcribe_file does the same for a file the agent holds, sent as fileBase64 with its filename. The decoded size is capped at 10 MiB, roughly ten minutes of 128 kbps MP3; for anything larger, give the agent a link.Both return immediately with the transcriptionId, state: "queued" and a next hint naming the wait tool. Agent-created transcriptions start private, with no share link.
An agent cannot sit through a live broadcast any more patiently than you can; it would keep calling the wait tool until someone took the laptop away. A finished recording has an end, and that is what the next pair of tools waits for.
A tool call is one HTTP request, and HTTP requests are not meant to last an hour. Clients give up on them, proxies close them, and a model that is blocked on one tool cannot do anything else. So the pattern for any long job behind MCP is the one the REST API uses too: start the job and get a handle, wait on it for a bounded time, and call again if it is not done. The handle, not the connection, carries the state.
wait_for_transcription blocks until the job is done or failed, or until timeoutS passes (default 50 seconds, maximum 60, which keeps the call under the timeouts most clients and proxies apply). If the result says timedOut: true, the job is still running and the agent calls the tool again. While the tool waits, the server sends MCP progress notifications if the client asked for them: a percentage and a phase such as "fetching 3.1 / 8.0 MB" or "transcribing 01:25 of 02:00".get_transcription returns the status and, once done, the text. format: "text" gives one line per segment as [mm:ss] Speaker 1: ...; "segments" gives timed lines with start, end, speaker and speakerName; includeWords: true adds word timings. Results are paged (offset, limit, default 500 segments) with a nextOffset when more remain.The paging is there for the model, not the network. Everything a tool returns is pasted into the agent's context window, where it competes with your conversation and costs tokens on every later turn. A two-hour meeting is tens of thousands of words; returning all of it at once would push the question you asked out of the context. Good MCP tools return a page and a cursor and let the agent decide how much it needs.
A disconnect or a timeout never cancels the job, because the job lives on the server, not in the MCP request. Once it is done, the job is one more entry in your library, and the next tools treat it like any other.
list_transcriptions lists the account's transcriptions, newest first, with state and duration; lifecycle is active, archived or all.search_transcriptions searches what was said across every transcription, in any language, and returns the matching lines, each with its transcription and the second it was spoken. transcriptionId narrows the search to one transcription; lang adds a translation of each hit.Finding a line is the cheap half. Two more tools work out what the whole thing said.
get_summary returns the AI title and summary of a finished transcription, optionally translated with lang. state: "pending" means the summary is still being written (it is usually ready within seconds of the job finishing), and the agent asks again.translate_transcription returns a finished transcript in another language, as text or timed segments, paged like get_transcription. Translations are stored, so asking again does not repeat the work.list_voices lists the voices that can read a transcript in a language: id, name, gender and accent when known, a quality rating, and whether it is the default or one of your cloned voices. It describes voices; it does not make one speak (more below on what the server does not do).Here is how the tools chain together in practice.
Once the server is added, a conversation in Claude Code goes like this. You paste a podcast link and ask for the gist in French.
The agent calls transcribe_url:
{ "url": "https://example.org/episodes/42.mp3", "name": "Episode 42", "speakerLabels": "auto" }
It gets back a transcriptionId and state: "queued", and follows the next hint:
{ "transcriptionId": "4f1c...", "timeoutS": 50 }
A fifty-minute episode is transcribed many times faster than it plays, but not inside one wait. The first wait returns timedOut: true with progress at, say, 61 percent, so the agent calls the tool once more and gets state: "done".
Now the agent calls get_summary with lang: "fr":
{ "transcriptionId": "4f1c...", "lang": "fr" }
If the summary is still pending, the agent waits a few seconds; otherwise it hands you a title and summary in French. Ask "where did they talk about pricing?" and it calls search_transcriptions with query: "pricing" and the same transcriptionId, and quotes the matching lines with their timestamps. Ask for the whole thing in French and it calls translate_transcription.
A handful of tool calls, no code, and a transcription in your library. Each of those calls went through the same gates as a REST request, and those gates are what set the bill and the limits.
The MCP server is the API through a different front door: same limits, same bill.
POST /v1/transcriptions: the plan's creation rate (10, 60 or a custom number of transcriptions started per hour), its concurrent-transcription cap (1, 5 or unlimited) and its credit all apply. The $5 signup credit covers a first session, no card needed.isError: true with the REST API's error code, a sentence the model can act on, and retryable and retryAfterS fields. insufficient_credit carries a topUpUrl; rate_limited says how long to wait. This matters more than it sounds: a model reads the error text, so a sentence it can act on turns a dead end into the next tool call./v1/mcp request itself: a looping agent gets an HTTP 429 with Retry-After before any tool runs.idempotencyKey: reusing the key of a completed call replays the first result, and reusing it with different arguments is refused, so an agent that retries on its own never starts a second job. Any tool that spends money should offer this; agents retry far more freely than people do.Those are the edges of what the server does. Two things fall outside them on purpose.
The server has no tool that turns arbitrary text into speech, and none is planned. VocalStack speaks transcripts and their translations, in a stock or cloned voice, and nothing else. list_voices tells an agent which voices can read a transcript in a language, not how to make a voice say something new.
The transcribe tools also refuse live streams. A YouTube live stream or a radio stream needs a live transcription, started from the Dashboard or through POST /v1/transcriptions/live in the API reference. A finished YouTube video is a file, and transcribe_url takes it.
If you would rather write the code yourself, transcribing a file with the API walks through the same steps as HTTP calls, and the real-time tutorial covers the WebSocket.