یک سرور MCP، یک API را به چند ابزار نامدار تبدیل میکند که عامل وسط گفتگو صدایشان میزند، و وقتی ابزار دستگیرهای بدهد که عامل با نوبتهای محدود منتظرش بماند، یک کار طولانی هم در این چارچوب جا میگیرد. یک لینک پادکست بچسبانید، خلاصه را به فرانسوی بخواهید، و هیچ کدی نوشته نمیشود.
یک عامل کدنویسی میتواند از API ویکالاستک همانطور استفاده کند که هر برنامهای میکند: مرجع API را بخواند، چند خط کد با SDK بنویسد و اجرایش کند. برای کاری که بدون عامل اجرا میشود، همین مسیر درست است.
MCP (پروتکل زمینه مدل) برای حالت دیگری است: با یک دستیار حرف میزنید و میخواهید همان لحظه، بدون کد، چیزی را رونویسی کند. یک سرور MCP فهرست کوتاهی از ابزارهای نامدار منتشر میکند که هر کدام توضیح و طرحواره JSON آرگومانهایشان را دارند. عامل فهرست را یک بار میخواند و وقتی درخواستی با یکی از ابزارها جور شود، آن را با نامش صدا میزند. «این پادکست را رونویسی کن و بگو درباره قیمتگذاری چه گفتهاند» میشود سه فراخوانی ابزار، و عامل هرگز یک مسیر HTTP را نمیبیند.
سرورها دو نوعاند. سرور محلی بهعنوان زیرفرایند کلاینت اجرا میشود و از طریق stdio ارتباط میگیرد؛ بیشتر سرورهای سیستم فایل و پایگاه داده همینطورند. سرور راهدور پشت یک نشانی اینترنتی قرار دارد و با Streamable HTTP کار میکند؛ این همان انتقالی است که در نسخه مارس 2025 مشخصات، جای انتقال قدیمیتر HTTP-plus-SSE (رویدادهای ارسالشده از سرور) را گرفت. کلاینت هر درخواست را ارسال میکند و سرور پاسخ را روی همان اتصال میدهد؛ هر چیزی که سرور پیش از نتیجه نهایی باید بگوید، پیش از آن بهصورت جریانی فرستاده میشود. هر کلاینتی که مطابق مشخصات فعلی نوشته شده باشد، میتواند از هر دو نوع استفاده کند.
سرور VocalStack از نوع راهدور است و در نشانی {{api}}/v1/mcp قرار دارد؛ استفاده از آن همان هزینهای را دارد که API دارد. تنها چیزی که لازم دارید همین نشانی است.
بخش Developers داشبورد را در {{app}}/developers باز کنید. تب اول، Connect an AI agent، نشانی سرور و قطعه راهاندازی هر کلاینت را نشان میدهد. سه قطعه زیر با قطعههایی که آنجا نشان داده میشوند یکیاند.
این را در ترمینالتان اجرا کنید:
claude mcp add --transport http vocalstack {{api}}/v1/mcp
این را به .cursor/mcp.json در پروژهتان اضافه کنید، یا برای همه پروژهها به ~/.cursor/mcp.json:
{"mcpServers":{"vocalstack":{"url":"{{api}}/v1/mcp"}}}
هر کلاینتی که Streamable HTTP را پشتیبانی کند، میتواند سرور را با این نشانی اضافه کند. این شکل کلی است که داشبورد نشان میدهد:
{
"vocalstack": {
"type": "streamable-http",
"url": "{{api}}/v1/mcp"
}
}
برای شروع کلید API لازم ندارید. دفعه اول که کلاینت وصل میشود، با حساب ویکالاستکتان در مرورگر وارد میشوید و کلاینت توکن را نگه میدارد. داشبورد این را در یک خط میگوید: «دفعه اول با حساب VocalStack خود وارد میشوید. به کلید نیازی نیست.»
این کار به مشخصات احراز هویت MCP برمیگردد، نه به ترفند ویکالاستک. سرور راهدور به درخواست بیهویت با پاسخ 401 جواب میدهد که به فراداده منبع محافظتشدهاش اشاره میکند: یک سند JSON کوچک که نام سرور احراز هویت و محدودههایی را که سرور لازم دارد مشخص میکند. کلاینت آن را میخواند، یک جریان استاندارد OAuth 2.1 را در مرورگر اجرا میکند و با توکنی برمیگردد که فقط برای همان سرور صادر شده است. هر سرور MCP راهدوری که از مشخصات پیروی کند، به همین شکل ورود را انجام میدهد؛ به همین دلیل کلاینتهای بالا میتوانند بدون دانستن چیزی درباره ویکالاستک این کار را بکنند.
کلید API هم کار میکند، برای کلاینتی که روی یک سرور یا در یک اسکریپت اجرا میشود. آن را بهصورت Authorization: Bearer vs_key_... بفرستید؛ همان هدری که REST API میگیرد. کلیدها زیر Developers → API keys ساخته میشوند. دو ابزار رونویسی به دسترسیهای Write و Stream audio نیاز دارند. دومی برای یک فایل عجیب به نظر میرسد، اما این ابزارها از همان نقطه ساخت که هر رونویسی از آن میگذرد عبور میکنند، از جمله رونویسیهای زنده، و آن نقطه هر دو را بررسی میکند. ابزارهای خواندن به Read نیاز دارند؛ list_voices فقط به یک اعتبار معتبر نیاز دارد.
وقتی وارد شدید، عامل نه ابزار میبیند. شما بهندرت خودتان نامشان را میبرید، اما دانستنشان توضیح میدهد که عامل بعد چه کاری میکند.
هر ابزار با حروف camelCase JSON میگیرد و برمیگرداند، با همان نام فیلدهای نوعهای /v1؛ دستگیره یک رونویسی، transcriptionId آن است.
transcribe_url رونویسی صدا یا ویدیو را از یک نشانی عمومی شروع میکند: لینک یک فایل، یک قسمت پادکست، یک ویدیوی YouTube. پخشهای زنده پذیرفته نمیشوند. آرگومانهای اختیاری: name، spokenLanguage (اگر آن را نگذارید زبان تشخیص داده میشود)، speakerLabels (auto، on یا off) و startS/endS برای رونویسی فقط بخشی از صدا.transcribe_file همین کار را برای فایلی انجام میدهد که عامل در اختیار دارد؛ با fileBase64 و filename فرستاده میشود. اندازه پس از رمزگشایی تا 10 MiB محدود است، یعنی تقریباً ده دقیقه MP3 با 128 kbps؛ برای چیزهای بزرگتر، به عامل یک لینک بدهید.هر دو بلافاصله با transcriptionId، state: "queued" و یک راهنمای next برمیگردند که نام ابزار انتظار را مشخص میکند. رونویسیهایی که عامل میسازد بهصورت خصوصی شروع میشوند و لینک اشتراک ندارند.
یک عامل نمیتواند از یک پخش زنده صبورانهتر از شما عبور کند؛ مدام ابزار انتظار را صدا میزند، تا وقتی کسی لپتاپ را از دستش بگیرد. یک ضبط تمامشده پایان دارد، و جفت ابزارهای بعدی دقیقاً منتظر همان پایان است.
یک فراخوانی ابزار یک درخواست HTTP است، و درخواستهای HTTP قرار نیست یک ساعت طول بکشند. کلاینتها از آنها دست میکشند، پروکسیها اتصال را میبندند، و مدلی که منتظر یک ابزار مانده، کار دیگری نمیتواند بکند. پس الگوی هر کار طولانی پشت MCP همان چیزی است که API REST هم دارد: کار را شروع کنید و یک دستگیره بگیرید، برای مدت محدودی منتظرش بمانید، و اگر تمام نشده بود دوباره صدا بزنید. دستگیره است که وضعیت را حمل میکند، نه اتصال.
wait_for_transcription تا وقتی کار به done یا failed برسد یا timeoutS بگذرد، مسدود میماند (پیشفرض 50 ثانیه، حداکثر 60، که فراخوانی را زیر زمانبندیهایی نگه میدارد که بیشتر کلاینتها و پروکسیها اعمال میکنند). اگر نتیجه timedOut: true داشته باشد، کار هنوز در حال اجراست و عامل دوباره ابزار را صدا میزند. وقتی ابزار منتظر است و کلاینت درخواستش را کرده باشد، سرور اعلانهای پیشرفت MCP میفرستد: یک درصد و یک فاز، مثل «در حال دریافت 3.1 از 8.0 MB» یا «در حال رونویسی 01:25 از 02:00».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 صداهایی را فهرست میکند که میتوانند متن رونویسی را به یک زبان بخوانند: شناسه، نام، جنسیت و لهجه (در صورت شناختهبودن)، رتبه کیفیت، و اینکه صدای پیشفرض است یا یکی از صداهای شبیهسازیشده شما. این ابزار صداها را توصیف میکند؛ کاری نمیکند که یکیشان چیز تازهای بگوید (بیشتر درباره آنچه سرور انجام نمیدهد، پایینتر).در عمل ابزارها اینطور به هم وصل میشوند.
وقتی سرور اضافه شد، گفتگو در Claude Code اینطور پیش میرود. یک لینک پادکست میچسبانید و خلاصهاش را به فرانسوی میخواهید.
عامل transcribe_url را صدا میزند:
{ "url": "https://example.org/episodes/42.mp3", "name": "Episode 42", "speakerLabels": "auto" }
یک transcriptionId و state: "queued" برمیگردد و راهنمای next را دنبال میکند:
{ "transcriptionId": "4f1c...", "timeoutS": 50 }
یک قسمت پنجاهدقیقهای پادکست خیلی سریعتر از پخش شدنش رونویسی میشود، اما نه در یک انتظار. اولین انتظار با timedOut: true و پیشرفتی حدوداً 61 درصد برمیگردد، پس عامل یک بار دیگر صدا میزند و state: "done" میگیرد.
حالا عامل get_summary را با lang: "fr" صدا میزند:
{ "transcriptionId": "4f1c...", "lang": "fr" }
اگر خلاصه هنوز pending باشد، عامل چند ثانیه صبر میکند؛ وگرنه عنوان و خلاصه را به فرانسوی تحویل میدهد. بپرسید «کجا درباره قیمتگذاری حرف زدند؟» و عامل search_transcriptions را با query: "pricing" و همان transcriptionId صدا میزند، و خطوط منطبق را با زمانهایشان نقل میکند. اگر کل متن را به فرانسوی بخواهید، 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 هم اعمال میشود: عامل حلقهای با HTTP 429 و Retry-After مواجه میشود، پیش از اجرای هر ابزاری.idempotencyKey میگیرند: استفاده دوباره از کلید یک فراخوانی کاملشده، نتیجه اول را دوباره پخش میکند، و استفاده از آن با آرگومانهای متفاوت رد میشود، پس عاملی که خودش دوباره تلاش میکند هرگز یک کار دوم شروع نمیکند. هر ابزاری که پول خرج میکند باید این را ارائه دهد؛ عاملها بسیار آزادانهتر از آدمها دوباره تلاش میکنند.اینها لبههای کاری است که سرور انجام میدهد. دو چیز هم عمداً بیرون از این لبهها قرار دارند.
سرور هیچ ابزاری ندارد که متن دلخواه را به گفتار تبدیل کند، و هیچکدام هم برنامهریزی نشده است. ویکالاستک فقط متنهای رونویسی و ترجمههایشان را، با یک صدای آماده یا شبیهسازیشده، میخواند و غیر از آن کاری نمیکند. list_voices به عامل میگوید کدام صداها میتوانند متن یک رونویسی را به یک زبان بخوانند، نه اینکه چطور یک صدا را وادار کند چیز تازهای بگوید.
ابزارهای رونویسی پخشهای زنده را هم نمیپذیرند. یک پخش زنده YouTube یا یک استریم رادیویی به یک رونویسی زنده نیاز دارد که از داشبورد یا از طریق POST /v1/transcriptions/live در مرجع API شروع شود. یک ویدیوی YouTube تمامشده یک فایل است و transcribe_url آن را میپذیرد.
اگر ترجیح میدهید کد را خودتان بنویسید، رونویسی یک فایل با API همان مراحل را بهصورت فراخوانی HTTP شرح میدهد، و آموزش بلادرنگ WebSocket را پوشش میدهد.