یک پرتاب سکه در توزیعکنندهٔ بار (Load Balancer) اکنون میتواند کل نشست فعال یک ابزار هوش مصنوعی را نابود کند. این پیامد مستقیم بهروزرسانی مشخصات هستهٔ پروتکل زمینهٔ مدل (Model Context Protocol یا MCP) در ۲۸ جولای ۲۰۲۶ است که رشتهٔ «نشست» (Session) را — که پیشتر به سرور اجازه میداد پیشرفت کاربر را بین فراخوانیها به یاد آورد — بهطور کامل حذف کرد.
این تغییر، بار حافظه را از دوش سرور به دوش کلاینت منتقل میکند. در معماری قدیمی، سرور میدانست کاربر در کجای یک جستوجو متوقف شده است و این وضعیت را در حافظه داخلی خود نگه میداشت؛ اما اکنون کلاینت باید در هر درخواست، یک شناسه صریح — مانند ID جستوجو و مکاننما (Cursor) — را مانند یک برگه شناسایی همراه خود به سرور بازگرداند تا سرور بتواند متوجه شود درخواست مربوط به کدام عملیات است. همانطور که در تحلیلهای پیشین ما دربارهی استقرار مدلهای زبانی اشاره کردیم، حذف وابستگی به یک گره خاص، اولین قدم برای مقیاسپذیری واقعی است.
برای توسعهدهندگانی که عاملهایی مانند Anzuelo را میسازند — عاملی که برای یافتن گفتگوهای مرتبط، پلتفرمهایی مثل ردیت (Reddit)، Hacker News، Bluesky، Mastodon، یوتیوب، لینکدین و Threads را میکاود — این تغییر مرز بین یک سیستم آمادهٔ تولید و سیستمی است که در لحظهٔ مقیاسپذیری کرش میکند. طبق مستندات فنی، Anzuelo از API مدل Claude برای امتیازدهی به یافتههای خود استفاده میکند. در حال حاضر، این عامل به صورت یک عملیات زمانبندیشده روزانه با تکپردازش و بدون همروندی (Concurrency) اجرا میشود. اما به محض اینکه این گردشکار «جستوجو-و-صفحهبندی» برای ابزارهای دیگر باز شود تا آنها بتوانند در صورت نیاز (On-demand) آن را فراخوانی کنند، فرض اینکه «سرور به یاد دارد شما کجا بودید» به یک ریسک مرگبار تبدیل میشود.
تصور کنید دو نسخه از یک سرور پشت یک توزیعکننده بار دارید: اگر پاد A جستوجو را شروع کند اما پاد B درخواست «صفحه بعدی» را دریافت کند، پاد B هیچ خاطرهای از آن جستوجو ندارد. نتیجه یک خطا یا کرش نیست، بلکه یک پاسخ «بنبست» (Dead end) است؛ پیامی که با کمال ادب اما به طور موفقیتآمیز به کلاینت میگوید هیچ جستوجوی فعالی وجود ندارد. بر اساس گزارشهای توسعهدهندگان در بازه زمانی پیش از تغییرات ۲۸ جولای، این رایجترین دلیل شکست در سیستمهای MCP بود.
مکانیسمهای بهروزرسانی ۲۸ جولای
در نسخه قبلی MCP، گفتگو با یک «سلام» شروع میشد. کلاینت و سرور پیامی به نام initialize رد و بدل میکردند تا روی نسخه و قابلیتهای هر یک توافق کنند. این کار یک رشته پایدار به نام نشست (Session) ایجاد میکرد که سرور آن را در پسزمینه و زیربنای تمام فراخوانیهای بعدی نگه میداشت.
مشخصات جدید ۲۸ جولای این مرحلهٔ «سلام» و رشتهٔ نشست را بهطور کامل حذف کرده است. اکنون هر درخواست باید تمام «بار» و اطلاعات لازم برای پردازش را با خود حمل کند. این اطلاعات شامل موارد زیر است:
- اطلاعات نسخه: نسخه دقیقی که کلاینت با آن صحبت میکند تا سازگاری تضمین شود.
- هویت فراخوان: اطلاعات مربوط به اینکه چه کسی یا چه سیستمی در حال ارسال درخواست است.
- لیست قابلیتهای کلاینت: فهرستی جامع از کارهایی که کلاینت قادر به انجام آنهاست.
چون هیچ توافقی یکبار صورت نمیگیرد که برای دفعات بعد به خاطر سپرده شود، حجم داده در هر فراخوانی افزایش یافته است. با این حال، مزیت استراتژیک این است که هیچ درخواستی وابسته به رسیدن به همان ماشین فیزیکی قبلی نیست. این یعنی سرورها میتوانند در محیطهای «ارزان» که اتصالات باز (Open Connections) را نگه نمیدارند اجرا شوند و مقیاسپذیری در پادهای کوبرنتیز (Kubernetes) — که در آن هر کپی از سرور یک پاد نامیده میشود — کاملاً بینقص و بدون حالت (Stateless) شود.
نقش کلاینت در معماری جدید
برای درک این شکست، باید معماری کلی MCP را شناخت. این معماری از سه جزء اصلی تشکیل شده است:
- مدل: دستیاری هوشمند (مانند Claude) که کاربر در حال گفتگو با آن است.
- سرور MCP: یک برنامه کوچک که توابع خاصی را ارائه میدهد که در اینجا «ابزارها» (Tools) نامیده میشوند.
- کلاینت: لایه میانی که وظیفه انتقال پیامها بین مدل و سرور را بر عهده دارد.
نکته کلیدی و حیاتی این است که مدل هرگز کد سرور را مستقیماً اجرا نمیکند. اگر مدل بخواهد جستوجویی برای اشارهها (Mentions) انجام دهد، صرفاً پیامی میفرستد: «call start_search, keyword is mcp». کلاینت این پیام را به سرور میرساند. سرور ابزار را اجرا کرده و متن نتیجه را برمیگرداند. هر یک از این مراحل یک رفتوبرگشت جداگانه است؛ یعنی: درخواست، پاسخ، پایان. در این آرایش، هیچ تضمینی وجود ندارد که سوال بعدی به همان نسخهٔ در حال اجرای سرور برسد که سوال اول را پاسخ داده بود.
پیادهسازی فنی: قدیم در برابر جدید
در یک پیادهسازی سادهانگارانه (Naive)، توسعهدهنده ممکن است از یک دیکشنری مشترک در حافظه سرور برای ردیابی مکاننمای جستوجو استفاده کند، چیزی شبیه به: _search_state: dict[str, dict] = {}. به نقل از متخصصان سیستمهای توزیعشده، این کار به دو دلیل بسیار خطرناک است.
اول، مشکل توزیعکننده بار است که پیشتر ذکر شد. دوم، یک نقص جدی در همروندی (Concurrency) وجود دارد: اگر وضعیت (State) فقط یک خانه با کلیدی ساده مثل «current» باشد، این وضعیت به یک فراخوان خاص محدود (Scoped) نیست. اگر دو کلاینت همزمان به یک پردازش واحد برسند، هر کسی که آخرینبار دستور start_search را اجرا کرده باشد، برنده میشود و مکاننمای کاربر قبلی بیصدا به مجموعه نتایج شخص دیگری میپرد. این وضعیت حتی از «پین کردن نشست» خطرناکتر است چون در درون یک پردازش واحد و بهطور نامحسوس رخ میدهد و باعث نشت داده یا گم شدن نتایج میشود.
برای مثال، تبادل JSON-RPC (فرمت پیامهای MCP) بر روی stdio در حالت قدیمی به این شکل بود:
۱. کلاینت start_search را با کلمه کلیدی «mcp» صدا میزند.
۲. سرور متنی را برمیگرداند که میگوید ۳ مورد یافت شد: «جستوجو برای 'mcp' شروع شد. ۳ مورد یافت شد. برای مشاهده صفحات بعدی get_next_page() را فراخوانی کنید».
۳. کلاینت get_next_page را با آرگومانهای خالی میفرستد: {"arguments": {}}.
تنها دلیل کار کردن این فراخوانی دوم این است که آن پردازش خاص، مکاننما را در حافظه محلی خود نگه داشته است. اگر این درخواست به پاد دیگری برسد، با شکست مواجه میشود. این دقیقاً به این دلیل است که «روش قدیمی» بر پایه اعتماد به یک پردازش خاص برای به خاطر سپردن نیازهای کلاینت بود.
بازتعریف قرارداد ابزار
برای حل این مشکل، «راه جدید» شامل بازتعریف امضاهای ابزار (Tool Signatures) است. به جای اینکه ابزاری مثل get_next_page() بدون آرگومان باشد، اکنون باید شناسههای خاصی را به صورت اجباری بپذیرد. در مدل بهروزرسانیشده، ابزار start_search یک SearchHandle (که یک TypedDict است) برمیگرداند که شامل search_id (شناسه جستوجو)، cursor (مکاننما)، match_count (تعداد совпаденияها) و یک message است. ابزار get_next_page اکنون به طور صریح به search_id: str و cursor: int به عنوان آرگومان ورودی نیاز دارد.
این پیادهسازی بر این اساس است که کلاینت تداوم (Continuity) را حفظ کند. در یک دموی کوچک، search_id ممکن است صرفاً همان کلمه کلیدی باشد و سرور در هر فراخوانی نتایج را از یک لیست استاتیک دوباره محاسبه کند. اما در یک محیط عملیاتی (Production)، سرور یک ID نامفهوم (Opaque ID) تولید میکند و مجموعه نتایج را در یک ذخیرهساز مشترک نگه میدارد که تمام نسخههای سرور قادر به خواندن آن باشند.
کشف TypedDict
پیادهسازی این تغییر، یک نکته ظریف در SDK را درباره نحوه درک مدل از Handle آشکار میکند. اگر توسعهدهنده خروجی ابزار را به صورت یک dict ساده تعریف کند، FastMCP (بهویژه نسخه ۱.۲۹.۰) طرحوارهای (Schema) برای ساختن ندارد. در نتیجه، SDK مقدار بازگشتی را به صورت متن ساده در فیلد content میریزد و فیلد structuredContent مقدار null میگیرد.
این یعنی Handle فقط به صورت یک رشته (String) وجود دارد که کلاینت باید قبل از خواندن حتی یک فیلد، آن را تجزیه (Parse) کند. برای حل این مشکل، توسعهدهندگان باید از TypedDict برای اعلام دقیق شکل پاسخ استفاده کنند. با تعریف انواع SearchHandle و MentionPage است که SDK میتواند یک شیء تمیز و ساختاریافته در فیلد structuredContent ارائه دهد.
یک تفاوت کلیدی در اینجا این است که حاشیه نویسی قدیمی -> str مقادیر را زیر یک کلید به نام result میپیچد، در حالی که نسخه TypedDict فیلدها را به صورت یک شیء عریان (Bare Object) برمیگرداند. علاوه بر این، TypedDict شکلی ثابت را تحمیل میکند؛ برای مثال، در MentionPage فیلدهای platform و text زمانی که done برابر با true باشد، صراحتاً روی null تنظیم میشوند، به جای اینکه تعداد کلیدهای متفاوتی برگردانده شود.
سایر تغییرات پروتکل
علاوه بر مدیریت وضعیت، بهروزرسانی ۲۸ جولای استانداردهای دیگری را برای مقاومتر کردن پروتکل معرفی کرد:
- احراز هویت (Auth): اکنون با الگوهای استاندارد OAuth همراستا شده است، که به سرورها اجازه میدهد به جای اختراع راهکارهای سفارشی، به سیستمهای شناسایی شرکتی موجود متصل شوند.
- برنامههای MCP (MCP Apps): قابلیت جدیدی که به سرورها اجازه میدهد به جای متن ساده، یک رابط تعاملی کوچک (Interactive Interface) ارسال کنند.
- وظایف (Tasks): پشتیبانی از کارهایی که برای تکمیل زمان زیادی نیاز دارند. سرور یک Handle برای تکلیف برمیگرداند و کلاینت با استفاده از آن Handle، پیشرفت کار را نظارت (Poll) میکند. این دقیقاً مشابه منطق
search_idوcursorاست. - قابلیتهای منسوخشده: بخشهای Roots، Sampling و Logging اکنون منسوخ شدهاند. این موارد برای یک بازه ۱۲ ماهه در مشخصات باقی میمانند تا اطمینان حاصل شود که در تاریخ ۲۸ جولای هیچ چیزی خراب نشده است و سپس کاملاً حذف میشوند.
تحلیل: درس سیستمهای توزیعشده
این بهروزرسانی کمتر درباره هوش مصنوعی و بیشتر درباره یک قانون بنیادین در سیستمهای توزیعشده است: هرگز به یک پردازش خاص اعتماد نکنید که چیزی را برای کلاینت به یاد آورد. MCP با حذف «عکاز نشست»، توسعهدهندگان AI را مجبور میکند تا خیلی زود بهترین روشهای بکاند (Backend) را یاد بگیرند. تفکر درباره وضعیت (State) و طراحی قراردادهای تمیز بین دو سیستم، مهارتهای محوری هستند که اغلب در مصاحبههای سطح ورودی توسعهدهندگان تست میشوند.
برای توسعهدهنده، این یعنی «قرارداد» بین کلاینت و سرور باید صریح و شفاف باشد. مزیت این کار، داشتن یک پشته (Stack) هوش مصنوعی است که میتواند بدون شکنندگی به صورت افقی مقیاس پذیرد. اگر شما از یک پردازش واحد روی لپتاپ استفاده میکنید، متوجه این تغییر نخواهید شد؛ اما به محض انتقال به محیط ابری با چندین پاد، رویکرد بدونحالت (Stateless) تنها راه برای جلوگیری از شکستهای بیصدا است.
مسیر رسیدن به تولید
برای تبدیل یک سرور اسباببازی با پنج رشته سختافزاری (Hardcoded) به یک سیستم عملیاتی واقعی با دادههای واقعی در Supabase، سه تغییر معماری مشخص لازم است:
۱. شناسههای نامفهوم (Opaque IDs): مقدار search_id باید یک ID تولیدشده و نامفهوم باشد، نه کلمه کلیدی خام. این کار از نشت عبارت جستوجو جلوگیری کرده و تضمین میکند که ID توسط کاربران دیگر قابل حدس زدن نباشد.
۲. وضعیت جهانی (Global State): مجموعههای نتایج باید در یک ذخیرهساز در دسترس جهانی (مانند Redis یا Postgres) باشند که هر کپی از سرور بتواند آن را بخواند.
۳. اعتبارسنجی Handle: تابع get_next_page باید قبل از دسترسی به هرگونه داده، ID را اعتبارسنجی کند و به جای اعتماد کورکورانه به آرگومانهای ارسالی کلاینت، صحت آنها را بسنجد.
توسعهدهندگان باید نسخه SDK خود را چک کنند؛ توجه داشته باشید که در نسخه ۲.x نام FastMCP به MCPServer تغییر یافته است. اگرچه API دکوراتورها پس از تغییر نام باقی ماندهاند، اما مسیر Import تغییر کرده است، به این معنی که نصبهای بدون تعیین نسخه (Unpinned) با خطا مواجه خواهند شد. راهنماهای مهاجرت برای این تغییر توسط AAIF و وبلاگ رسمی MCP منتشر شده است.
گام بعدی شما
- اگر از نسخههای قدیمی
FastMCPاستفاده میکنید، فوراً آرگومانهای توابعget_next_pageرا به حالت صریح (Explicit) تغییر دهید تا شاملsearch_idوcursorشود. - بررسی کنید که آیا وضعیتهای کاربر را در حافظه محلی (Local Memory) سرور ذخیره کردهاید یا از یک لایه Redis برای مدیریت وضعیت توزیعشده استفاده میکنید.
- برای دریافت خروجیهای ساختاریافته در MCP، به جای
dictازTypedDictاستفاده کنید تا دادهها به درستی در فیلدstructuredContentقرار بگیرند و توسط کلاینت قابل شناسایی باشند.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو