MCP 서버는 대화 중에 에이전트가 호출하는 이름 붙은 몇 개의 도구로 API를 바꿔 줍니다. 도구가 에이전트가 제한된 턴 안에서 기다릴 수 있는 핸들을 돌려주면, 오래 걸리는 작업도 그 뒤에 자연스럽게 맞춰집니다. 팟캐스트 링크를 붙여 넣고 프랑스어로 요약을 요청해 보세요. 코드는 한 줄도 쓰지 않습니다.
코딩 에이전트는 어떤 프로그램과 마찬가지로 VocalStack API를 사용할 수 있습니다. API 레퍼런스를 읽고, SDK로 몇 줄을 작성해 실행하면 됩니다. 에이전트 없이 돌아갈 작업이라면 이것이 올바른 방법입니다.
MCP(Model Context Protocol)는 다른 경우를 위한 것입니다. 어시스턴트와 대화하면서 코드 없이 지금 바로 무언가를 전사하고 싶을 때죠. MCP 서버는 이름이 붙은 짧은 도구 목록을 공개합니다. 각 도구에는 설명과 인자용 JSON 스키마가 함께 있습니다. 에이전트는 이 목록을 한 번 읽고, 요청이 맞을 때 도구 이름으로 호출합니다. "이 팟캐스트를 전사하고 가격에 대해 뭐라고 했는지 알려 줘"라는 요청은 도구 호출 세 번으로 바뀌며, 에이전트는 HTTP 경로를 전혀 보지 않습니다.
서버는 두 종류가 있습니다. 로컬 서버는 클라이언트의 하위 프로세스로 실행되며 stdio로 통신합니다. 대부분의 파일 시스템 및 데이터베이스 서버가 이 방식입니다. 원격 서버는 URL 뒤에 있으며 Streamable HTTP를 사용합니다. 이 방식은 2025년 3월 사양 개정에서 기존의 HTTP+SSE(서버 전송 이벤트) 전송 방식을 대체했습니다. 클라이언트는 요청을 보내고, 서버는 같은 연결로 응답합니다. 최종 결과 전에 서버가 전할 내용은 그보다 앞서 스트리밍됩니다. 현재 사양에 맞춰 작성된 클라이언트라면 두 종류 모두 사용할 수 있습니다.
VocalStack 서버는 원격 방식이며 주소는 {{api}}/v1/mcp입니다. 사용 요금은 API와 같습니다. 필요한 것은 URL 하나뿐입니다.
{{app}}/developers에서 대시보드의 Developers 영역을 엽니다. 첫 번째 탭인 Connect an AI agent에는 서버 URL과 클라이언트별 설정 스니펫이 표시됩니다. 아래의 세 스니펫은 그곳에 표시된 것과 같습니다.
터미널에서 다음을 실행하세요:
claude mcp add --transport http vocalstack {{api}}/v1/mcp
프로젝트의 .cursor/mcp.json에, 또는 모든 프로젝트에 적용하려면 ~/.cursor/mcp.json에 다음을 추가하세요:
{"mcpServers":{"vocalstack":{"url":"{{api}}/v1/mcp"}}}
Streamable HTTP를 지원하는 클라이언트라면 이 URL로 서버를 추가할 수 있습니다. 다음은 대시보드에 표시되는 일반 형식입니다:
{
"vocalstack": {
"type": "streamable-http",
"url": "{{api}}/v1/mcp"
}
}
시작하는 데 API 키는 필요하지 않습니다. 클라이언트가 처음 연결할 때 브라우저에서 VocalStack 계정으로 로그인하면, 클라이언트가 토큰을 보관합니다. 대시보드에는 이를 한 줄로 안내합니다: "처음에는 VocalStack 계정으로 로그인합니다. 키는 필요하지 않습니다."
이것은 VocalStack만의 기법이 아니라 MCP 인증 사양이 작동하는 방식입니다. 원격 서버는 인증되지 않은 요청에 401 응답을 보내며, 이때 보호된 리소스 메타데이터를 가리킵니다. 이는 인가 서버와 서버가 요구하는 스코프를 명시한 작은 JSON 문서입니다. 클라이언트는 이를 읽고 브라우저에서 표준 OAuth 2.1 흐름을 실행한 뒤, 이 서버만을 위해 발급된 토큰을 받아 돌아옵니다. 사양을 따르는 원격 MCP 서버라면 모두 같은 방식으로 로그인하므로, 위의 클라이언트들은 VocalStack에 대해 아무것도 모르고도 이를 수행할 수 있습니다.
API 키도 사용할 수 있습니다. 서버나 스크립트에서 실행되는 클라이언트에 적합합니다. Authorization: Bearer vs_key_... 형식으로 보내면 되며, REST API가 받는 것과 같은 헤더입니다. 키는 Developers → API keys에서 만듭니다. 전사 도구 두 개에는 Write와 Stream audio 권한이 필요합니다. 파일에 대해 두 번째 권한은 어색해 보이지만, 이 도구들은 라이브 전사를 포함한 모든 전사와 같은 생성 지점을 거치며, 그 지점이 두 권한을 모두 확인합니다. 읽기 도구는 Read가 필요하고, list_voices는 유효한 인증 정보만 있으면 됩니다.
로그인하고 나면 에이전트에는 도구 아홉 개가 보입니다. 직접 이름을 부르는 일은 드물겠지만, 이 도구들을 알아두면 에이전트가 다음에 무엇을 하는지 이해할 수 있습니다.
모든 도구는 camelCase JSON을 입력받고 반환하며, 필드 이름은 /v1 와이어 타입과 같습니다. 전사의 핸들은 transcriptionId입니다.
transcribe_url은 공개 URL의 오디오나 비디오 전사를 시작합니다. 대상은 파일 링크, 팟캐스트 에피소드, YouTube 영상입니다. 실시간 스트림은 받지 않습니다. 선택 인자는 name, spokenLanguage(생략하면 언어를 자동 감지), speakerLabels(auto, on 또는 off), 그리고 오디오의 일부만 전사하는 startS/endS입니다.transcribe_file은 에이전트가 가진 파일에 대해 같은 일을 합니다. filename과 함께 fileBase64로 보냅니다. 디코딩된 크기는 10 MiB로 제한되며, 128 kbps MP3로 약 10분 분량입니다. 그보다 큰 파일이라면 에이전트에 링크를 주세요.두 도구 모두 즉시 transcriptionId, state: "queued"와 대기 도구를 가리키는 next 힌트를 반환합니다. 에이전트가 만든 전사는 공유 링크가 없는 비공개 상태로 시작합니다.
에이전트도 라이브 방송을 끝까지 기다리는 데 사람보다 더 참을성이 있지는 않습니다. 대기 도구를 계속 호출하다가 누군가 노트북을 가져갈 때까지 이어질 겁니다. 끝난 녹음에는 끝이 있고, 다음 두 도구가 기다리는 것이 바로 그것입니다.
도구 호출은 HTTP 요청 한 번이며, HTTP 요청은 한 시간씩 유지되도록 설계되지 않았습니다. 클라이언트는 포기하고, 프록시는 연결을 닫으며, 도구 하나에 막힌 모델은 다른 일을 할 수 없습니다. 그래서 MCP 뒤에서 돌아가는 긴 작업의 패턴은 REST API도 쓰는 방식과 같습니다. 작업을 시작해 핸들을 받고, 정해진 시간만큼 기다렸다가 끝나지 않았으면 다시 호출합니다. 상태를 전달하는 것은 연결이 아니라 핸들입니다.
wait_for_transcription은 작업이 done 또는 failed가 될 때까지, 또는 timeoutS가 지날 때까지 차단합니다(기본값 50초, 최대 60초로, 대부분의 클라이언트와 프록시가 적용하는 제한 시간 안에 호출이 머물도록 한 값입니다). 결과에 timedOut: true가 있으면 작업은 아직 실행 중이며, 에이전트는 도구를 다시 호출합니다. 도구가 기다리는 동안, 클라이언트가 요청했다면 서버는 MCP 진행 알림을 보냅니다. 백분율과 "3.1 / 8.0 MB 가져오는 중" 또는 "02:00 중 01:25 전사하는 중" 같은 단계가 담깁니다.get_transcription은 상태를 반환하고, 완료되면 텍스트도 반환합니다. format: "text"는 세그먼트마다 한 줄씩 [mm:ss] Speaker 1: ... 형식으로 줍니다. "segments"는 start, end, speaker, speakerName이 포함된 시간 정보 줄을 줍니다. includeWords: true는 단어 단위 시간 정보를 추가합니다. 결과는 페이지 단위로 나뉘며(offset, limit, 기본값 세그먼트 500개), 더 남아 있으면 nextOffset이 함께 반환됩니다.페이지 나누기는 네트워크가 아니라 모델을 위한 것입니다. 도구가 반환하는 모든 내용은 에이전트의 컨텍스트 창에 붙여 넣어지며, 그곳에서 대화와 경쟁하고 이후 매 턴마다 토큰 비용을 발생시킵니다. 두 시간짜리 회의는 수만 단어입니다. 이를 한꺼번에 반환하면 질문이 컨텍스트 밖으로 밀려날 것입니다. 좋은 MCP 도구는 페이지와 커서를 반환하고, 얼마나 필요한지는 에이전트가 정하게 합니다.
연결이 끊기거나 시간이 초과되어도 작업은 취소되지 않습니다. 작업은 MCP 요청이 아니라 서버에 존재하기 때문입니다. 작업이 끝나면 라이브러리에 항목이 하나 더 생기고, 다음 도구들은 이를 다른 항목과 똑같이 다룹니다.
list_transcriptions는 계정의 전사를 최신순으로 상태와 길이와 함께 나열합니다. lifecycle은 active, archived 또는 all입니다.search_transcriptions는 모든 전사에서 언어를 가리지 않고 말해진 내용을 검색하고, 일치하는 줄을 각각의 전사와 발화된 초와 함께 반환합니다. transcriptionId는 검색을 한 전사로 좁히고, lang은 각 결과에 번역을 추가합니다.줄을 찾는 것은 비교적 쉬운 절반입니다. 나머지 두 도구는 전체가 무슨 말을 했는지 파악합니다.
get_summary는 완료된 전사의 AI 제목과 요약을 반환하며, lang으로 번역할 수도 있습니다. state: "pending"은 요약이 아직 작성 중이라는 뜻이며(보통 작업이 끝난 후 몇 초 안에 준비됩니다), 에이전트는 다시 요청합니다.translate_transcription은 완료된 전사본을 다른 언어로 반환합니다. 텍스트 또는 시간 정보 세그먼트 형식이며, get_transcription과 같이 페이지로 나뉩니다. 번역은 저장되므로 다시 요청해도 작업이 반복되지 않습니다.list_voices는 특정 언어로 전사본을 읽을 수 있는 음성을 나열합니다. 음성의 id, 이름, 성별, 알려진 경우 억양, 품질 등급, 그리고 기본 음성인지 또는 내가 복제한 음성인지를 보여 줍니다. 음성을 설명할 뿐, 음성이 말하게 하지는 않습니다(서버가 하지 않는 일은 아래에서 더 설명합니다).실제로 도구들이 어떻게 이어지는지 보겠습니다.
서버를 추가하고 나면 Claude Code에서의 대화는 다음과 같이 진행됩니다. 팟캐스트 링크를 붙여 넣고 프랑스어로 요점을 요청합니다.
에이전트가 transcribe_url을 호출합니다:
{ "url": "https://example.org/episodes/42.mp3", "name": "Episode 42", "speakerLabels": "auto" }
transcriptionId와 state: "queued"를 받고, next 힌트를 따릅니다:
{ "transcriptionId": "4f1c...", "timeoutS": 50 }
50분짜리 에피소드는 재생 시간보다 훨씬 빠르게 전사되지만, 한 번의 대기 안에서는 끝나지 않습니다. 첫 번째 대기는 진행률이 예를 들어 61%인 상태로 timedOut: true를 반환하므로, 에이전트는 도구를 한 번 더 호출해 state: "done"을 받습니다.
이제 에이전트가 lang: "fr"을 넣어 get_summary를 호출합니다:
{ "transcriptionId": "4f1c...", "lang": "fr" }
요약이 여전히 pending이면 에이전트는 몇 초 기다리고, 그렇지 않으면 프랑스어 제목과 요약을 전해 줍니다. "가격에 대해서는 어디에서 이야기했지?"라고 물으면, 같은 transcriptionId와 query: "pricing"으로 search_transcriptions를 호출하고, 일치하는 줄을 시간 표시와 함께 인용합니다. 전체를 프랑스어로 달라고 하면 translate_transcription을 호출합니다.
도구 호출 몇 번, 코드는 없고, 라이브러리에 전사본 하나가 생깁니다. 이 호출 각각은 REST 요청과 같은 관문을 통과했고, 요금과 한도를 정하는 것이 바로 그 관문입니다.
MCP 서버는 다른 입구를 통하는 API입니다. 한도도 같고 요금도 같습니다.
POST /v1/transcriptions와 같은 생성 지점을 거칩니다. 따라서 요금제의 생성 속도(시간당 시작하는 전사 10개, 100개 또는 사용자 지정 개수), 동시 전사 상한(1개, 5개 또는 무제한), 그리고 크레딧이 모두 적용됩니다. 가입 시 주어지는 $5 크레딧으로 첫 세션을 사용할 수 있으며, 카드는 필요하지 않습니다.isError: true와 REST API의 오류 코드, 모델이 실행할 수 있는 문장, 그리고 retryable, retryAfterS 필드를 반환합니다. insufficient_credit에는 topUpUrl이 포함되고, rate_limited는 얼마나 기다려야 하는지 알려 줍니다. 생각보다 중요한 부분입니다. 모델은 오류 텍스트를 읽으므로, 실행 가능한 문장 하나가 막다른 길을 다음 도구 호출로 바꿔 줍니다./v1/mcp 요청 자체에도 적용됩니다. 반복하는 에이전트는 어떤 도구가 실행되기 전에 Retry-After가 포함된 HTTP 429를 받습니다.idempotencyKey를 받습니다. 완료된 호출의 키를 재사용하면 처음 결과를 다시 돌려주며, 다른 인자와 함께 재사용하면 거부됩니다. 따라서 스스로 재시도하는 에이전트가 두 번째 작업을 시작하는 일은 없습니다. 돈을 쓰는 도구라면 모두 이 기능을 제공해야 합니다. 에이전트는 사람보다 훨씬 자유롭게 재시도하기 때문입니다.이상이 서버가 하는 일의 경계입니다. 의도적으로 경계 밖에 있는 것이 두 가지 있습니다.
서버에는 임의의 텍스트를 음성으로 바꾸는 도구가 없으며, 그런 도구도 계획되어 있지 않습니다. VocalStack은 전사본과 그 번역본을 기본 음성이나 복제 음성으로 읽을 뿐, 그 밖의 일은 하지 않습니다. list_voices는 에이전트에게 어떤 음성이 특정 언어의 전사본을 읽을 수 있는지 알려 줄 뿐, 음성이 새로운 말을 하도록 만드는 방법은 알려 주지 않습니다.
전사 도구는 라이브 스트림도 거부합니다. YouTube 라이브 스트림이나 라디오 스트림에는 실시간 전사가 필요하며, 이는 대시보드에서 시작하거나 API 레퍼런스의 POST /v1/transcriptions/live를 통해 시작해야 합니다. 끝난 YouTube 영상은 파일이므로 transcribe_url이 처리합니다.
직접 코드를 작성하고 싶다면, API로 파일 전사하기에서 같은 단계를 HTTP 호출로 안내하고, 실시간 튜토리얼은 WebSocket을 다룹니다.