فاز 13 · درس 6

مبانی MCP: درخواست‌های بی‌وضعیت و JSON-RPC

نوع
مفهومی
زمان
حدود 55 دقیقه
زبان‌ها
Python
نمایی سه‌بعدی از بسته‌های داده مستقل و درخشان که روی مسیرهای موازی بدون اتصال به یکدیگر حرکت می‌کنند و برچسب‌های اطلاعاتی مجزا دارند
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفاهیم پایه
  4. سازه‌های پایه‌ی سرور
  5. ساختار بسته‌های JSON-RPC
  6. فراداده‌های الزامی درخواست
  7. نتایج کامل و هویت سرور
  8. کشف بدون دست‌دادن
  9. چرخه‌ی عمر یک درخواست
  10. سازگاری صریح با نسخه‌های قدیمی
  11. پیاده‌سازی عملی
  12. ارائه‌ی نهایی
  13. تمرین‌ها
  14. اصطلاحات کلیدی
  15. برای مطالعه‌ی بیشتر

در سیستم‌های توزیع‌شده و مقیاس‌پذیر هوش مصنوعی، تکیه بر نشست‌های دائمی اتصال (Connection Sessions) یکی از بزرگ‌ترین چالش‌های همگام‌سازی و مدیریت بار است. نسخه مدرن پروتکل زمینه مدل (Model Context Protocol یا MCP) با حذف کامل نشست‌های پروتکلی و مکانیزم سنتی دست‌دادن (Handshake)، رویکردی کاملاً بی‌وضعیت (Stateless) را پیش گرفته است.

این تغییر به این معناست که هر درخواست ارسالی روی شبکه باید کاملاً خودتوصیف باشد؛ یعنی تمامی فراداده‌های لازم برای تعیین نسخه، قابلیت‌های کلاینت، و احراز صلاحیت را درون فیلد `_meta` در ساختار JSON-RPC 2.0 حمل کند. تسلط بر این ساختار برای توسعه‌دهندگانی که می‌خواهند ابزارها و سرویس‌های مقاوم در برابر قطعی و مستقل از وضعیت زیرساخت طراحی کنند، ضروری است.

نسخه‌ی مدرن MCP هیچ دست‌دادن (Handshake) و نشست پروتکلی ندارد. هر درخواست باید به‌تنهایی فراداده‌ی کافی برای درک‌شدن، بررسی مجوز، مسیریابی و تلاش مجدد به همراه داشته باشد.

اهداف یادگیری

  • تفکیک سازه‌های پایه‌ی سرور (Server Primitives) در MCP از قابلیت‌های سمت کلاینت.
  • ساخت درخواست‌ها و پاسخ‌های معتبر JSON-RPC 2.0 برای نسخه‌ی 2026-07-28 پروتکل MCP.
  • پیوست‌کردن نسخه‌ی پروتکل، قابلیت‌های کلاینت و هویت کلاینت به تک‌تک درخواست‌ها.
  • استفاده از server/discover و مدیریت خطای UnsupportedProtocolVersionError بدون نیاز به دست‌دادن.
  • ردگیری یک درخواست مستقل، از اعتبارسنجی تا بازگرداندن نتیجه‌ی کامل.

صورت مسئله

یک سرور MCP ممکن است دو درخواست متوالی از دو کلاینت مختلف، با قابلیت‌های متفاوت، روی یک فرایند (Process) یا ورکر HTTP یکسان دریافت کند. اگر سرور آن‌چه را که در درخواست قبلی اعلام شده بود به خاطر بسپارد، ممکن است دسترسی‌های اشتباهی اعمال کند یا ساختار داده‌ی خروجی روی شبکه را اشتباه بفرستد.

نسخه‌ی 2026-07-28 در MCP این ابهام را برطرف می‌کند. هسته‌ی پروتکل بی‌وضعیت (Stateless) است. سرور باید درباره‌ی نحوه‌ی پردازش درخواست فعلی، صرفاً بر اساس همان درخواست تصمیم بگیرد، نه بر اساس تاریخچه‌ی اتصال.

این رویکرد مدل ذهنی را تغییر می‌دهد. در توالی قدیمی، اول اتصال برقرار می‌شد، سپس دست‌دادن انجام می‌گرفت و در آخر عملیات اجرا می‌شد. توالی مدرن ساده‌تر است:

  1. کلاینت یک درخواست خودتوصیف می‌فرستد.
  2. سرور نسخه و قابلیت‌های موجود در آن درخواست را اعتبارسنجی می‌کند.
  3. سرور متد را اجرا می‌کند.
  4. سرور یک نتیجه‌ی نوع‌دار (Typed) یا خطای JSON-RPC برمی‌گرداند.

درخواست بعدی نیز همین فرایند را از ابتدا تکرار می‌کند.

مفاهیم پایه

سازه‌های پایه‌ی سرور

سرورهای MCP سه سازه‌ی اصلی ارائه می‌دهند:

  1. ابزارها (Tools): عملیات تحت کنترل مدل هستند که با tools/list شناسایی و با tools/call فراخوانی می‌شوند.
  2. منابع (Resources): داده‌های دارای آدرس URI هستند که با resources/list شناسایی و با resources/read دریافت می‌شوند.
  3. پرامپت‌ها (Prompts): الگوهای بازکاربردپذیر هستند که با prompts/list شناسایی و با prompts/get رندر می‌شوند.

مفاهیم Roots، Sampling و Logging صرفاً برای سازگاری در اسکیمای 2026-07-28 باقی مانده‌اند، اما منسوخ (Deprecated) شده‌اند. پیاده‌سازی‌های جدید باید برای Roots از ورودی‌های صریح ابزار یا منبع، برای Sampling از APIهای مستقیم ارائه‌دهندگان مدل، و برای Logging از stderr یا OpenTelemetry استفاده کنند. قابلیت استخراج اطلاعات (Elicitation) همچنان از طریق «درخواست‌های چند رفت‌وبرگشتی» (Multi Round-Trip Requests) در دسترس است؛ به‌طوری‌که سرور یک درخواست ورودی برمی‌گرداند و کلاینت عملیات اصلی را دوباره تکرار می‌کند. سرور مدرن هرگز خودش درخواست JSON-RPC مستقلی را آغاز نمی‌کند.

ساختار بسته‌های JSON-RPC

پروتکل MCP از JSON-RPC 2.0 استفاده می‌کند:

  • درخواست: {jsonrpc, id, method, params}
  • پاسخ: {jsonrpc, id, result} یا {jsonrpc, id, error}
  • اعلان (Notification): {jsonrpc, method, params} بدون id

شناسه‌ی (id) درخواست، صرفاً پاسخ را به همان درخواست مرتبط می‌کند و نشست پروتکلی نمی‌سازد.

فراداده‌های الزامی درخواست

هر درخواست مدرن یک آبجکت _meta درون params حمل می‌کند:

{
  "jsonrpc": "2.0",
  "id": 7,
  "method": "tools/list",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": {
        "name": "course-client",
        "version": "1.0.0"
      }
    }
  }
}

نسخه‌ی پروتکل و قابلیت‌های کلاینت الزامی هستند. هویت کلاینت پیشنهادی است؛ این فیلد اطلاعاتی نمایشی برای دیباگ است که خود کلاینت گزارش می‌دهد، نه یک مدرک امنیتی.

سرور نباید هیچ‌کدام از این مقادیر را صرفاً از درخواست قبلی، فرایند stdio، اتصال HTTP یا هدر بستر انتقال (Transport) استنتاج کند.

نتایج کامل و هویت سرور

تمام نتایج موفق در پروتکل مدرن شامل resultType هستند. یک نتیجه‌ی نهایی معمولی مقدار "complete" دارد. سرورها همچنین باید هویت خود را در فراداده‌ی نتیجه اعلام کنند:

{
  "jsonrpc": "2.0",
  "id": 7,
  "result": {
    "resultType": "complete",
    "tools": [],
    "ttlMs": 30000,
    "cacheScope": "public",
    "_meta": {
      "io.modelcontextprotocol/serverInfo": {
        "name": "notes-server",
        "version": "1.0.0"
      }
    }
  }
}

متدهای tools/list، resources/list، prompts/list، resources/templates/list، resources/read و server/discover نتایج کش‌پذیر دارند و شامل ttlMs و cacheScope می‌شوند. یک پیش‌فرض امن ttlMs: 0 و cacheScope: "private" است. آیتم‌های لیست باید ترتیب قطعی (Deterministic) داشته باشند تا پاسخ‌های معادل، کلیدهای کش پایدار و کانتکست باثباتی برای مدل ایجاد کنند.

کشف بدون دست‌دادن

همه‌ی سرورهای مدرن باید server/discover را پیاده‌سازی کنند. کلاینت می‌تواند پیش از فراخوانی هر متد دیگری، آن را برای دریافت موارد زیر فراخوانی کند:

  • supportedVersions
  • قابلیت‌های سرور (capabilities)
  • راهنمای استفاده‌ی اختیاری (instructions)
  • هویت سرور در بخش _meta نتیجه
  • راهنماهای کش (Cache hints)

فرایند کشف (Discovery) مفید است، اما سد راه نیست. کلاینت می‌تواند همان ابتدا tools/list را بفرستد، چون آن درخواست خودش نسخه‌ی پروتکل و قابلیت‌هایش را همراه دارد.

اگر نسخه‌ی درخواستی پشتیبانی نشود، سرور کد خطای JSON-RPC به شماره‌ی -32022 را همراه با اطلاعات زیر برمی‌گرداند:

{
  "requested": "2027-01-01",
  "supported": ["2026-07-28"]
}

سپس کلاینت نسخه‌ی مدرنی را انتخاب می‌کند که هر دو طرف پشتیبانی می‌کنند و درخواست را با یک id جدید در JSON-RPC تکرار می‌کند.

چرخه‌ی عمر یک درخواست

یک درخواست مدرن را به این ترتیب ردگیری کنید:

  1. بسته‌ی JSON-RPC را پارس کنید.
  2. مطمئن شوید مقدار jsonrpc برابر "2.0" است، id وجود دارد، method رشته متنی است و params یک آبجکت است.
  3. وجود رشته‌ی نسخه و آبجکت قابلیت‌ها را در params._meta الزامی بدانید؛ فراداده‌ی ناقص یا ناموجود خطای -32602 دارد.
  4. در مرز ارتباط HTTP، هدرهای نسخه، متد و هدرهای نام مرتبط را با بدنه مقایسه کنید. عدم تطابق، خطای -32020 است؛ حتی اگر یکی از این دو مقدار نسخه پشتیبانی‌نشده باشد.
  5. بعد از اطمینان از یکسان‌بودن، اگر نسخه‌ی تطبیق‌یافته پشتیبانی نمی‌شود، آن را با -32022 رد کنید.
  6. قابلیت‌های الزامی را بررسی کنید، سپس بر اساس method مسیردهی انجام دهید و آرگومان‌های اختصاصی متد را بسنجید.
  7. پیش از اجرای هندلر (Handler)، عملیات مشخص‌شده را احراز هویت و مجوزسنجی کنید.
  8. یک نتیجه‌ی کامل همراه با هویت سرور برگردانید.
  9. فراداده‌های پروتکلِ مربوط به این درخواست را از حافظه پاک کنید.

این ترتیب مانع از آن می‌شود که دو بخش مختلف سیستم، فراخوانی‌ها را به دو شکل متفاوت تفسیر کنند. نباید گیت‌وی به Mcp-Name: notes.read مجوز بدهد در حالی که سرور مبدا در حال اجرای params.name: notes.delete است. علاوه بر این، ورودی نامعتبر، تداخل هدرها، مذاکره‌ی نسخه، نقص قابلیت‌ها، خطای مجوز و خطای هندلر هرکدام شواهد (Evidence) مجزا و مشخصی باقی می‌مانند.

بستن stdin یا پاسخ HTTP به فعالیت بستر انتقال پایان می‌دهد، اما نشست پروتکل را نمی‌بندد؛ چون MCP مدرن اصلاً نشست پروتکلی ندارد.

سازگاری صریح با نسخه‌های قدیمی

نسخه‌های تا تاریخ 2025-11-25 از initialize، اعلان notifications/initialized، قابلیت‌های وابسته به اتصال، و در نسخه‌های اولیه‌ی Streamable HTTP، از نشست‌های اختیاری پروتکل استفاده می‌کنند. این رفتارها هنوز هنگام ارتباط یک کلاینت دومنظوره با سروری قدیمی کاربرد دارند.

این دوره‌ها را کاملاً جدا نگه دارید. درخواست مدرن از روی فراداده‌های الزامی درون هر درخواست مشخص می‌شود. اتصال به شیوه‌ی قدیمی نیز فقط از مسیر جایگزینِ (Fallback) مستندشده انتخاب می‌شود. هرگز به‌صورت پیش‌فرض برای یک سرور نسخه‌ی 2026-07-28 درخواست initialize نفرستید.

بنابراین «بی‌وضعیت» در هر دوره معنای متفاوتی دارد. در نسخه‌ی 2026-07-28، این یک اصل تغییرناپذیر در پروتکل است: هر درخواست عادی به‌طور مستقل تفسیرپذیر است و هیچ نشست MCPای وجود ندارد. اما در نسخه‌های تا تاریخ 2025-11-25، آماده‌سازی اولیه و قابلیت‌های توافق‌شده به اتصال تعلق دارند؛ بنابراین یک تطبیق‌دهنده (Adapter) سازگاری می‌تواند این وضعیت اتصال قدیمی را نگه دارد. پیاده‌سازی سازگار با هر دو دوره به معنای ساخت یک ماشین وضعیت (State Machine) سهل‌گیر نیست؛ بلکه قرار دادن یک هسته‌ی مدرن بی‌وضعیت در کنار یک تطبیق‌دهنده‌ی مجزای قدیمی است، به‌طوری که پیش از اجرای هر کدام از پارسرها، تصمیمی صریح برای انتخاب میان آن‌ها گرفته شود.

هیچ‌کدام از این دو تعریف، مانع از ذخیره‌ی وضعیت پایدار برنامه (Application State) نیستند. گردش کار، تسک یا پیش‌نویس می‌تواند پشت یک شناسه‌ی مات (Opaque handle) در فضایی ذخیره‌سازی مشترک قرار بگیرد. کلاینت این شناسه را به عنوان ورودی معمولی می‌فرستد و تک‌تک رپلیکاها (Replicas) استفاده از آن را احراز هویت و مجوزسنجی می‌کنند. نباید کانتکست پروتکل وارد این فضای ذخیره‌سازی شود تا جای خالی نشستِ حذف‌شده را پر کند.

پیاده‌سازی عملی

فایل code/main.py پیام‌های مدرن MCP را بدون نیاز به هیچ فریم‌ورکی می‌سازد، اعتبارسنجی و ردگیری می‌کند و به مقصد می‌فرستد. دستور زیر را اجرا کنید:

python3 code/main.py
python3 -m unittest discover code/tests -v

در خروجی برنامه، به سه اصل تغییرناپذیر دقت کنید:

  • تک‌تک درخواست‌ها فیلدهای _meta خود را تکرار می‌کنند.
  • تک‌تک نتایج موفق، دارای resultType: "complete" بوده و هویت سرور را دارند.
  • نتیجه‌ی لیست‌ها ترتیب کاملاً قطعی دارد و راهنماهای کش به‌طور صریح در آن آمده‌اند.

ارائه‌ی نهایی

خروجی این درس فایل outputs/skill-mcp-handshake-tracer.md است. نام قدیمی فایل حفظ شده، اما ماهیت آن حالا یک ردگیر درخواست‌های بی‌وضعیت است. این ابزار هر پیام را مستقل بررسی می‌کند و ترافیک مربوط به دست‌دادن سنتی را فقط زمانی برچسب‌گذاری می‌کند که واقعاً وجود داشته باشد.

تمرین‌ها

  1. نسخه‌ی پروتکل یکی از درخواست‌ها را به 2027-01-01 تغییر دهید. بررسی کنید که کد خطای -32022 برگردد و داده‌های خطا، نسخه‌ی پشتیبانی‌شده را معرفی کنند.
  2. کلید io.modelcontextprotocol/clientCapabilities را از درخواست دوم حذف کنید. مطمئن شوید سرور از قابلیت‌های اعلام‌شده در درخواست اول استفاده نمی‌کند.
  3. ترتیب ذخیره‌ی ابزارها را در رجیستری حافظه معکوس کنید. بررسی کنید که tools/list همچنان همان خروجی با ترتیب قطعی را برگرداند.
  4. مقدار cacheScope را از public به private تغییر دهید. توضیح دهید در هر حالت، کدام کانتکست‌های احراز مجوز مجاز به استفاده‌ی مجدد از پاسخ هستند.
  5. تستی برای حذف فیلد اختیاری clientInfo بنویسید. درخواست باید معتبر بماند، چون اطلاعات هویت کلاینت صرفاً پیشنهادی است و الزامی نیست.

اصطلاحات کلیدی

اصطلاح معنا
پروتکل بی‌وضعیت (Stateless protocol) هر درخواست، فراداده‌های لازم برای تفسیر خودش را به همراه دارد
فراداده‌ی درخواست (Request metadata) نسخه، قابلیت‌های کلاینت و هویت پیشنهادی کلاینت در params._meta
server/discover متد الزامی سرور برای نسخه‌ها، قابلیت‌ها، دستورالعمل‌ها و هویت
resultType تفکیک‌کننده‌ی وضعیت در تک‌تک نتایج موفق مدرن
نتیجه‌ی کش‌پذیر (Cacheable result) پاسخی که حاوی راهنماهای الزامی ttlMs و cacheScope است
دوره‌ی پروتکل (Protocol era) فراداده‌های مدرن در هر درخواست یا آماده‌سازی اولیه‌ی قدیمی وابسته به اتصال
طول عمر بستر انتقال (Transport lifetime) طول عمر فرایند، اتصال یا استریم پاسخ؛ جدا از وضعیت نشست پروتکل
-32022 خطای نسخه‌ی پشتیبانی‌نشده‌ی پروتکل همراه با نسخه‌ی درخواستی و نسخه‌های پشتیبانی‌شده

برای مطالعه‌ی بیشتر

خودآزمایی

به سؤال‌ها جواب بدهید تا ببینید چقدر از درس در ذهنتان مانده است.

  1. پیش از درسچه چیزی وضعیت پروتکل را بین دو درخواست MCP 2026-07-28 ایجاد می‌کند؟
  2. حین درسکدام دو فیلد params._meta در هر درخواست مدرن الزامی هستند؟
  3. حین درسهر سرور MCP مدرن چه متدی را باید برای اکتشاف اولیه (up-front discovery) پیاده‌سازی کند؟
  4. حین درسکد خطای -32022 به چه معناست؟
  5. پس از درسکدام نتیجه در MCP 2026-07-28 به ttlMs و cacheScope نیاز دارد؟
  6. پس از درسیک سرور چگونه باید قابلیت‌ها را برای درخواست فعلی انتخاب کند؟

سؤالات متداول

چرا در نسخه مدرن MCP مفهوم Handshake و نشست پروتکلی حذف شده است؟

برای جلوگیری از تداخل مجوزها و تفاسیر متناقض سرور در مواجهه با درخواست‌های متوالی از کلاینت‌های مختلف روی یک ورکر مشترک؛ سرور باید هر درخواست را کاملاً مستقل از تاریخچه پردازش کند.

در پروتکل مدرن MCP، فراداده‌های الزامی کلاینت در کجا قرار می‌گیرند؟

این داده‌ها شامل نسخه پروتکل و قابلیت‌های کلاینت درون آبجکت `params._meta` در هر درخواست JSON-RPC فرستاده می‌شوند.

اگر نسخه پروتکل ارسالی توسط کلاینت پشتیبانی نشود، سرور چه پاسخی می‌دهد؟

سرور خطای استاندارد JSON-RPC با کد 32022- به همراه نسخه‌های پشتیبانی‌شده و نسخه درخواستی بازمی‌گرداند تا کلاینت با نسخه معتبر دوباره تلاش کند.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.