فاز 13 · درس 14

پیاده‌سازی MCP Apps روی پروتکل بی‌وضعیت

نوع
ساختنی
زمان
حدود 75 دقیقه
زبان‌ها
Python
پیش‌نیاز
فاز 13 · درس 07 (سرور MCP)، فاز 13 · درس 10 (منابع)
یک بوم شیشه‌ای نیمه‌شفاف شامل یک رابط کاربری شناور که درون یک جعبه شفاف ایزوله قرار دارد و رشته‌های نوری کنترل‌شده داده‌ها را به آن وارد می‌کنند.
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفاهیم پایه
  4. دو پروتکل در قالب یک قابلیت
  5. شناسایی پیش از رندر
  6. تعریف واسط کاربری در تعریف ابزار
  7. ارسال داده و واگذاری اتصال نما به میزبان
  8. ارائه‌ی برنامه در قالب یک منبع
  9. کش‌کردن منابع واسط کاربری به‌عنوان محتوای اجرایی
  10. اعتبارسنجی ترافیک خام پیش از سیاست‌های دسترسی
  11. سندباکس فقط یک مرز امنیتی است، نه تأیید اعتماد
  12. چرخه‌ی حیات مستقل رابط Apps
  13. بافت میزبان، اقدامات و لغو دسترسی
  14. حفظ سازگاری در غیاب واسط کاربری
  15. پیاده‌سازی
  16. استفاده عملی
  17. انتشار
  18. تمرین‌ها
  19. اصطلاحات کلیدی
  20. برای مطالعه‌ی بیشتر

مدل‌های زبانی و ابزارهای متنی سنتی می‌توانند اطلاعات را در قالب متون ساده یا جدول‌های محدود توصیف کنند، اما خروجی‌های تعاملی مانند نمودارهای پویا، خطوط زمانی قابل فیلتر یا پنل‌های عملیاتی نیازمند واسط کاربری هستند. افزونه‌ی MCP Apps بدون زیر پا گذاشتن استانداردهای وب، امکان اجرای امن رابط‌های کاربری تعاملی را در کلاینت‌های مدل‌های زبانی فراهم می‌کند.

در معماری جدید پروتکل MCP، نشست‌های طولانی‌مدت و دست‌دادن‌های سنگین حذف شده‌اند و ارتباطات به صورت بی‌وضعیت (stateless) انجام می‌شوند. این موضوع توسعه‌دهندگان را با چالش جدیدی مواجه می‌کند: چگونه می‌توان یک برنامه‌ی وب پویا را درون ابزاری فراخوانی کرد که هیچ نشست پایداری در سطح پروتکل ندارد؟

در این درس یاد می‌گیرید که چگونه افزونه‌ی Apps را از طریق `server/discover` معرفی کنید، منبع نمایشی با طرح شناسه `ui://` را پیش از فراخوانی ابزار مقید سازید و تبادل پیام‌ها را با رعایت اصول سندباکس، سیاست امنیت محتوا (CSP) و اعتبارسنجی دقیق مبدأ در کلاینت مدیریت کنید.

یک نتیجه‌ی تعاملی همچنان همان تبادل ابزار و منبع در پروتکل MCP است. هسته‌ی نسخه‌ی 2026-07-28 این تبادل را خودکفا (self-contained) می‌کند و افزونه‌ی Apps نیز بستر سندباکس‌شده‌ی مرورگر (sandboxed browser surface) را به آن می‌افزاید.

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

  • معرفی MCP Apps از طریق server/discover و قابلیت‌های افزونه به‌ازای هر درخواست.
  • تعریف منبع ui:// روی ابزار، پیش از فراخوانی آن.
  • بازگرداندن نتایج کامل ابزار و منبع بر بستر انتقال (transport) بی‌وضعیت (stateless) نسخه‌ی 2026-07-28.
  • تفکیک پیام رابطِ (bridge) ui/initialize در Apps از فرایند دست‌دادنِ (handshake) حذف‌شده در هسته‌ی MCP.
  • اِعمال اعتبارسنجی مبدأ، سندباکس، سیاست امنیت محتوا (CSP) و دسترسی‌های حداقل‌لازم (least-privilege).

صورت مسئله

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

افزونه‌ی MCP Apps این مشکل نمایش را با یک افزونه‌ی اختیاری حل می‌کند. تعریف ابزار (Tool Definition) به یک منبع ui:// اشاره دارد. میزبان می‌تواند پیش از اجرای ابزار، آن منبع را دریافت و بازبینی کند، آن را داخل یک iframe سندباکس‌شده رندر نماید و تمام اقدامات برنامه را از طریق یک رابطِ JSON-RPC هدایت کند.

پروتکل اصلی در نسخه‌ی 2026-07-28 تغییر کرده است. برنامه‌ی خود را در چرخه‌ی حیات اتصالات قدیمی نپیچید:

  • درخواست initialize و اعلان notifications/initialized در هسته‌ی پروتکل حذف شده‌اند.
  • هدر Mcp-Session-Id دیگر وجود ندارد.
  • هر درخواست، نسخه‌ی پروتکل و قابلیت‌های کلاینت را داخل params._meta ارسال می‌کند.
  • سرور متد server/discover را پیاده‌سازی می‌کند تا کلاینت‌ها بتوانند نسخه‌ها، قابلیت‌های اصلی و افزونه‌ها را بررسی کنند.
  • تمام نتایج موفق یک تفکیک‌کننده‌ی resultType دارند.
  • پروتکل Streamable HTTP به‌ازای هر درخواست از یک متد POST استفاده می‌کند. درگاه‌های GET و DELETE در نسخه‌ی مدرن خطای 405 برمی‌گردانند.

رابط Apps هنوز متدی به نام ui/initialize دارد، اما این متد متعلق به ساختار postMessage در iframe است و نشست هسته‌ی MCP را دوباره زنده نمی‌کند.

مفاهیم پایه

دو پروتکل در قالب یک قابلیت

مرز میان لایه‌ها را دقیق نگه دارید:

  1. هسته‌ی MCP وظیفه‌ی انتقال متدهای server/discover، tools/list، tools/call، resources/list و resources/read را بر عهده دارد.
  2. افزونه‌ی MCP Apps واسط کاربری را تعریف می‌کند و رابطِ میان iframe و میزبان را شکل می‌دهد.
  3. قوانین سندباکس مرورگر دسترسی‌های واسط کاربری را محدود می‌کنند.

شناسه‌ی این افزونه io.modelcontextprotocol/ui است و فعال‌سازی آن با توافق هر دو طرف انجام می‌شود. کلاینت در هر درخواست، پشتیبانی از افزونه را درون شیء قابلیت‌ها (capabilities) ارسال می‌کند:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "server/discover",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/ui": {}
        }
      },
      "io.modelcontextprotocol/clientInfo": {
        "name": "timeline-host",
        "version": "1.0.0"
      }
    }
  }
}

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

شناسایی پیش از رندر

نتیجه‌ی شناسایی در سرور، این افزونه را معرفی می‌کند:

{
  "resultType": "complete",
  "supportedVersions": ["2026-07-28"],
  "capabilities": {
    "tools": {},
    "resources": {},
    "extensions": {
      "io.modelcontextprotocol/ui": {}
    }
  },
  "ttlMs": 300000,
  "cacheScope": "public",
  "_meta": {
    "io.modelcontextprotocol/serverInfo": {
      "name": "timeline-app-server",
      "version": "2.0.0"
    }
  }
}

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

تعریف واسط کاربری در تعریف ابزار

قرارداد مدرن Apps، واسط کاربری را در tools/list به ابزار مقید می‌کند:

{
  "name": "notes_timeline",
  "description": "Render a timeline of notes.",
  "inputSchema": {
    "type": "object",
    "properties": {}
  },
  "_meta": {
    "ui": {
      "resourceUri": "ui://notes/timeline.html"
    }
  }
}

این فراداده عمداً پیش از فراخوانی ارسال می‌شود. به این ترتیب میزبان می‌تواند پیش از آنکه نتیجه‌ی ابزار نیازمند نمایش باشد، فایل HTML را پیش‌بارگذاری و کش کرده و از نظر امنیتی بازبینی کند. کدهای سازگار با نسخه‌های قبلی شاید کلیدهای متنی ساده و مسطح قدیمی را هم بپذیرند، اما سرورهای جدید باید ساختار تودرتوی _meta.ui.resourceUri را ارسال کنند.

متد tools/list در هسته‌ی فعلی قابل کش‌کردن است. پاسخ را به‌صورت مرتب‌شده‌ی قطعی (deterministic) و همراه با ttlMs و دامنه‌ی (scope) کش یعنی cacheScope بازگردانید. اگر ابزارهای در دسترس به‌ازای هر کاربر یا توکن فرق می‌کند، cacheScope را روی private بگذارید.

ارسال داده و واگذاری اتصال نما به میزبان

فراخوانی ابزار (Tool call) محتوای عادی را در کنار داده‌های ساختاریافته برمی‌گرداند:

{
  "resultType": "complete",
  "content": [
    {"type": "text", "text": "Timeline ready."}
  ],
  "structuredContent": {
    "notes": [
      {"id": "note-1", "title": "Discover", "created": "2026-07-28"}
    ]
  },
  "isError": false
}

میزبان از پیش می‌داند چه نمایی (View) به این ابزار تعلق دارد؛ بنابراین نیازی نیست صرفاً برای تکرار URI یک بلوک محتوایی جدید بسازید.

ارائه‌ی برنامه در قالب یک منبع

سرور قابلیت resources را در مرحله‌ی شناسایی معرفی می‌کند؛ بنابراین باید متد الزامی resources/list را هم پیاده‌سازی کند. آیتم‌های این لیست قطعی شامل URI استاندارد، نام ثابت، توضیحات و نوع MIME است. پاسخِ لیست درست مانند لیست قطعی ابزارها شامل resultType، فراداده‌ی هویت سرور، ttlMs و cacheScope خواهد بود.

میزبان resources/read را ارسال می‌کند. این درخواست در Streamable HTTP شامل موارد زیر است:

POST /mcp
MCP-Protocol-Version: 2026-07-28
Mcp-Method: resources/read
Mcp-Name: ui://notes/timeline.html

مقادیر هدر و بدنه‌ی JSON-RPC باید دقیقاً یکسان باشند. عدم تطابق آن‌ها خطای پروتکل -32020 را به دنبال دارد.

پاسخ شامل منبع HTML و راهنماهای کش (cache hints) است:

{
  "resultType": "complete",
  "contents": [
    {
      "uri": "ui://notes/timeline.html",
      "mimeType": "text/html;profile=mcp-app",
      "text": "<!doctype html>...",
      "_meta": {
        "ui": {
          "csp": {
            "connectDomains": [],
            "resourceDomains": [],
            "frameDomains": [],
            "baseUriDomains": []
          },
          "permissions": {}
        }
      }
    }
  ],
  "ttlMs": 60000,
  "cacheScope": "public"
}

کش‌کردن منابع واسط کاربری به‌عنوان محتوای اجرایی

منبع یک برنامه با یک متن معمولی قابل تعویض نیست. داده‌های کش‌شده‌ی آن می‌توانند کدهای رابط را اجرا کنند، داده‌های ابزار را رندر نمایند و کارهایی را با وساطت میزبان درخواست دهند. کلید کش را بر اساس URI استاندارد ui://، هویت و نسخه‌ی تأییدشده‌ی سرور، خلاصه‌ی محتوای منبع (digest) و در صورت وجود دامنه‌ی خصوصی برای کش، بافت احراز هویت تعیین کنید. هرگز یک منبع برنامه‌ی خصوصی را میان کاربران مختلف به اشتراک نگذارید؛ چون حتی با یکسان بودن URI، ممکن است فایل HTML یا فراداده‌های امنیتی آن متفاوت باشد.

با منقضی‌شدن ttlMs، تغییر پیوند _meta.ui.resourceUri در ابزار، تغییر نسخه‌ی سرور یا شناسه‌ی تأییدشده، یا دریافت اعلان تغییر منبع برای آن URI، کش را باطل کنید. پیش از رندر مجدد، منبع را دوباره دریافت کرده و CSP و مجوزها را بازبینی کنید. یک iframe قدیمی نباید فقط به این دلیل که نسخه‌ی جدید هنوز بارگذاری نشده، دسترسی‌های گسترده‌تر خود را حفظ کند.

اعتبارسنجی ترافیک خام پیش از سیاست‌های دسترسی

اعتبارسنجی ترتیب مشخصی دارد: ابتدا ساختار JSON-RPC را بررسی کنید و مطمئن شوید فراداده‌های پروتکل از نوع رشته و نگاشت قابلیت‌های کلاینت از نوع شیء هستند. سپس هدرهای مسیربندی را با بدنه مطابقت دهید. فقط پس از آن بررسی کنید که آیا نسخه‌ی پروتکل تطبیق‌یافته پشتیبانی می‌شود یا خیر. این ترتیب مانع از تفسیر متناقض درخواست بین پروکسی و سرور می‌شود.

وضعیت کد HTTP خطای JSON-RPC
عدم تطابق نسخه، متد یا نام در هدر و بدنه 400 -32020
توافق هدر و بدنه روی نسخه‌ای پشتیبانی‌نشده 400 -32022، با مقدار دقیق data برابر با {"supported":["2026-07-28"],"requested":"<actual>"}
نبود قابلیت افزونه‌ی Apps در resources/read 400 -32021، با مسیر data.requiredCapabilities.extensions.io.modelcontextprotocol/ui
متد ناشناخته باشد 404 -32601

اعلان‌های JSON-RPC فیلد id ندارند؛ بنابراین سرور هرگز پاسخی در قالب JSON-RPC برای آن‌ها ارسال نمی‌کند. یک اعلان پذیرفته‌شده در بستر HTTP با وضعیت 202 و بدنه‌ی خالی پاسخ داده می‌شود. بروز خطا ممکن است وضعیت HTTP را تغییر دهد، اما باز هم نمی‌تواند بدنه‌ی خطای JSON-RPC برای اعلان‌ها بسازد.

سندباکس فقط یک مرز امنیتی است، نه تأیید اعتماد

میزبان کنترل کامل iframe را در دست دارد. برنامه نمی‌تواند کوکی‌ها، حافظه‌ی محلی یا DOM صفحه‌ی میزبان را مستقیماً بخواند. تمام کارهای نیازمند دسترسی باید از رابط عبور کنند.

این پیش‌فرض‌ها را به کار بگیرید:

  • تمام دامنه‌های مجاز در CSP را در ابتدا خالی بگذارید و فقط مبدأهای مورد نیاز برنامه را اضافه کنید. از connectDomains برای fetch، XHR و WebSocket و از resourceDomains برای اسکریپت‌ها، استایل‌ها، تصاویر و فونت‌ها استفاده کنید.
  • تا حد ممکن کدها و داده‌ها را بسته‌بندی (bundle) کنید.
  • هیچ مجوزی برای دوربین، میکروفون یا موقعیت مکانی نگیرید، مگر اینکه قابلیتی ملموس در برنامه به آن نیاز داشته باشد.
  • متد postMessage را دقیقاً به مبدأ طرف مقابل مقید کنید و رویدادهای سایر مبدأها را نپذیرید.
  • آرگومان‌های ابزار، نتایج آن، متن منبع و پیام‌های رابط را ورودی‌های نامطمئن در نظر بگیرید.
  • اخذ رضایت کاربر را درون خود میزبان نگه دارید. یک iframe نمی‌تواند اقدامات پیامددار را خودش تأیید کند.

یک ویژگی sandbox ثابت را از آموزش‌های دیگر در تمام میزبان‌ها کپی نکنید. میزبان باید پرچم‌های آن را متناسب با مدل مبدأ برنامه و ساختار ایزوله‌سازی خود تعیین کند.

یک دامنه‌ی مجاز همچنان می‌تواند مسیری برای نشت اطلاعات باشد. تنظیم connectDomains: ["https://api.example.com"] یعنی هر اسکریپتی داخل برنامه می‌تواند داده‌های مجاز را به آن آدرس بفرستد. تطبیق دقیق مبدأ جلوی ابهام در مقصد را می‌گیرد، اما مناسب‌بودن محتوای ارسالی را تعیین نمی‌کند. دسترسی‌های connect را به‌طور پیش‌فرض خالی بگذارید، از قرار دادن توکن‌های حامل (bearer tokens) در iframe خودداری کنید، تا جای ممکن عملیات محدود را از طریق میزبان هدایت (proxy) کنید، حجم درخواست‌ها و پاسخ‌ها را محدود نگه دارید و مشخص کنید هر درخواست بر اثر کدام اقدام کاربر ایجاد شده است. با resourceDomains جدا از connectDomains رفتار کنید؛ مجوز بارگذاری یک فونت یا اسکریپت نباید اجازه‌ی بارگذاری داده‌های دلخواه را صادر کند.

چرخه‌ی حیات مستقل رابط Apps

رابط Apps یک ساختار JSON-RPC بر بستر postMessage است. این ساختار می‌تواند پیام‌های ui/initialize و اعلان‌های ui/* را منتقل کند و متدهای شبیه به هسته‌ی پروتکل مثل tools/call را به عنوان پروکسی اجرا نماید.

نما (View) پیام ui/initialize را به همراه appInfo و شیء appCapabilities ارسال می‌کند. میزبان در پاسخ، قابلیت‌ها و بافت (context) خود را برمی‌گرداند. تنها پس از این پاسخ است که نما پیام ui/notifications/initialized را ارسال می‌کند. میزبان باید قبل از ارسال هر پیامی به نما، منتظر این اعلانِ Apps بماند.

این دست‌دادن (handshake) محلی، رابطی میان یک iframe و یک فریمِ میزبان می‌سازد. این کار نه نسخه‌ی پروتکل MCP را تعیین می‌کند، نه وضعیتی در سرور به وجود می‌آورد و نه نشستی بر بستر انتقال (transport) می‌سازد. به پیشوندها دقت کنید: اعلان هسته یعنی notifications/initialized حذف شده، در حالی که اعلانِ Apps یعنی ui/notifications/initialized باقی مانده است. هر درخواستی در هسته‌ی پروتکل که از طریق فراخوانی ابزار در رابط شکل می‌گیرد، درخواستی جدید و خودکفا با یک شناسه‌ی JSON-RPC تازه و فراداده‌های کامل است.

بافت میزبان، اقدامات و لغو دسترسی

میزبان حتی پس از راه‌اندازی رابط، مرجع نهایی باقی می‌ماند. نما تنها زمانی می‌تواند اجرای ابزار، جابه‌جایی در صفحات، استفاده از کلیپ‌بورد یا سایر کارهای حساس را درخواست کند که میزبان قابلیت آن را قبلاً اعلام کرده باشد. میزبان صحت درخواست نوع‌بندی‌شده، کاربر فعلی، مقصد و آرگومان‌ها را بررسی می‌کند، سیاست‌های تأیید را به کار می‌بندد و حتی می‌تواند آن را رد کند. کلیک روی دکمه و ارسال یک پیام معتبر در رابط فقط نیت کاربر (Intent) را نشان می‌دهند؛ هیچ‌کدام مجوزی برای اجرا صادر نمی‌کنند.

تنظیمات تم، اندازه و دسترسی‌پذیری را به چشم بافتی متغیر در میزبان ببینید، نه ورودی‌های ثابت در رندر اولیه:

  • توکن‌های رنگ و قلمِ ارائه‌شده توسط میزبان را اِعمال کنید و به تغییرات تم یا کنتراست واکنش نشان دهید.
  • اجازه دهید نما ابعاد مدنظرش را اعلام کند، اما کنترل ابعاد نهایی iframe را در دست بگیرید تا محتوا از چیدمان بیرون نزند یا لایه‌های فریبنده نسازد.
  • ترتیب کلیدهای کیبورد، نشانگر فوکوسِ مشهود، نام‌های استاندارد دسترسی‌پذیری، وضعیت صفحه‌خوان‌ها، کنتراست مناسب، بزرگ‌نمایی و رفتار کاهش حرکت (reduced-motion) را داخل iframe رعایت کنید.
  • انتقال فوکوس میان کنترل‌های میزبان و کنترل‌های نما را پس از تغییر اندازه و رندر مجدد دوباره بررسی کنید.

دسترسی‌ها ممکن است حین باز بودن برنامه لغو شوند؛ چه به دلیل تغییر حساب کاربری، تغییر سیاست‌ها، قرنطینه‌شدن یک سرور یا محدودشدن رضایت کاربر در میزبان. سطح دسترسی و مجوزها را در لحظه‌ی انجام هر اقدام ارزیابی کنید، نه فقط در مرحله‌ی ui/initialize. هنگام لغو دسترسی، فراخوانی‌های حساسِ در انتظار را رد کنید، فعالیت‌های شبکه‌ایِ خارج از چارچوب جدید را متوقف سازید، وضعیت‌های حساس رندرشده را پاک کنید و در صورتی که خودِ منبع واسط کاربری دیگر پذیرفته نیست، نما را دوباره سوار کرده یا به متن بازگردید. نما باید پاسخ رد را به‌عنوان یک نتیجه‌ی عادی بپذیرد، نه اینکه درخواست را آن‌قدر تکرار کند تا میزبان تسلیم شود.

حفظ سازگاری در غیاب واسط کاربری

سروری که از Apps پشتیبانی می‌کند، همچنان باید به میزبان‌هایی که قابلیت واسط کاربری را اعلام نمی‌کنند پاسخ دهد:

  • همان ابزار را در tools/list بدون _meta.ui برگردانید.
  • یک خروجی متنی کاربردی برای tools/call نگه دارید.
  • درخواست resources/read برای منبع واسط کاربری را با خطای کمبود قابلیت رد کنید.
  • برای تشخیص اتمام موفق یک ابزار، هرگز فرض نکنید که یک iframe حتماً وجود دارد.
محیط ایزوله برنامه‌های MCPui:// درون iframe، تبادل پیام از ورای حصار
MCP APPS SANDBOXA tool result names a ui:// resource whose HTML the host renders inside a sandboxed iframe. The frame gets a locked-down CSP and no network unless the metadata grants it, so the only way in or out is the tiny postMessage JSON-RPC dialect hopping over the sandbox wall. One HTML bundle renders the same in every compatible client.سرور MCPui://notes/timelineپنجره هاستبدون شبکه مگر با دسترسیiframe ایزولهCSP قفل‌شدهhtmlpostMessage
نتیجه یک ابزار نام منبع ui:// را تعیین می‌کند که هاست HTML آن را درون یک iframe ایزوله رندر می‌کند. این فریم از CSP قفل‌شده برخوردار است و دسترسی شبکه ندارد مگر آنکه فراداده اجازه دهد؛ بنابراین تنها راه ورود یا خروج، گویش کوچک JSON-RPC در قالب postMessage است که از دیوار سندباکس عبور می‌کند. یک بسته HTML در تمام کلاینت‌های سازگار به شکلی یکسان رندر می‌شود.

پیاده‌سازی

کد code/main.py یک مدل پروتکل درون‌پروسه‌ای و کوچک را بدون نیاز به SDK می‌سازد. این کد بسته‌ی درخواست‌ها و مقادیر مسیربندی در Streamable HTTP را اعتبارسنجی می‌کند، قابلیت‌های Apps را از طریق server/discover اعلام می‌نماید، ابزارها و منابع را فهرست می‌کند، ابزار را به اجرا درمی‌آورد و یک منبع HTML خودکفا تحویل می‌دهد.

این مدل داده‌های از پیش تجزیه‌شده و هدرهای مسیربندی را دریافت می‌کند؛ بنابراین یک تطبیق‌دهنده‌ی (Adapter) کامل HTTP نیست و Content-Type یا Accept را پردازش نمی‌کند. برای پیاده‌سازی کامل تطبیق‌دهنده‌ی Streamable HTTP که نیازمند هدر Content-Type: application/json و هدر Accept حاوی هر دو مقدار application/json و text/event-stream است، درس 09 را ببینید.

آن را اجرا کنید:

cd phases/13-tools-and-protocols/14-mcp-apps
python3 code/main.py
python3 -m unittest discover code/tests -v

پنج مورد را در خروجی بررسی کنید:

  1. تمام فراخوانی‌ها مستقل از هم هستند.
  2. تمام درخواست‌ها قابلیت‌های خود را در _meta ارسال می‌کنند.
  3. متد resources/list پیش از خواندن هر منبع، یک توصیف‌گر ثابت ارائه می‌دهد.
  4. تمام نتایج دارای resultType و فراداده‌های هویت سرور هستند.
  5. هیچ اثری از شناسه‌ی نشست هسته‌ی پروتکل دیده نمی‌شود.

استفاده عملی

کار را با server/discover شروع کنید. مطمئن شوید io.modelcontextprotocol/ui در نگاشت افزونه‌های سرور دیده می‌شود. سپس tools/list را دو بار فراخوانی کنید؛ یک بار با قابلیت Apps و بار دیگر بدون آن. پاسخ اول منبع واسط کاربری را اعلام می‌کند، در حالی که دومی به عنوان یک ابزار متنی ساده و کاربردی باقی می‌ماند.

منبع ui://notes/timeline.html را بخوانید. در متن HTML به دنبال hostOrigin و شرط محافظتی event.origin بگردید. وجود این دو خط، حداقل نشانه‌ی مشهودی است که نشان می‌دهد رابط برنامه از مقصدهای نامشخص (wildcard) استفاده نمی‌کند.

انتشار

این درس فایل outputs/skill-mcp-apps-spec.md را ارائه می‌دهد. پیش از نوشتن کدهای فریم‌ورک، از آن برای بازبینی قرارداد برنامه استفاده کنید. این فایل شما را ملزم می‌کند بسته‌ی کنونی هسته، مذاکره‌ی افزونه‌ها، حالت بازگشت به متن (fallback)، منبع واسط کاربری، سیاست‌های کش، CSP، مجوزها، متدهای رابط و مرزهای رضایت کاربر را دقیق و صریح مشخص کنید.

تمرین‌ها

  1. قابلیت کلاینت را به یک نگاشت خالی از افزونه‌ها تغییر دهید. مطمئن شوید tools/list ابزار را حفظ می‌کند اما پیوند واسط کاربری را حذف می‌نماید.
  2. هدر Mcp-Name: ui://notes/other.html را همراه با بدنه‌ای که خط زمانی را می‌خواند بفرستید و خطای -32020 را بررسی کنید.
  3. دامنه‌ی کش را به cacheScope: private تغییر دهید و شرطِ وابسته به کاربری را که این کار را توجیه می‌کند شرح دهید.
  4. اسکریپت را به آدرس https://static.example.com/app.js منتقل کنید. این مبدأ را در resourceDomains وارد نمایید و ریسک زنجیره‌ی تأمین ناشی از آن را توضیح دهید.
  5. یک ابزار به نام notes_open اضافه کنید و کلیک روی دکمه را از طریق میزبان هدایت نمایید. تصمیم‌گیری برای تأیید کاربر را درون خود میزبان نگه دارید.

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

اصطلاح معنی
MCP Apps افزونه‌ای اختیاری برای نمایش تعاملی HTML توسط میزبان MCP
io.modelcontextprotocol/ui شناسه‌ی افزونه که هر دو طرف آن را اعلام می‌کنند
ui:// طرح شناسه (scheme) برای قالب واسط کاربری برنامه
text/html;profile=mcp-app نوع MIME اختصاصی برای صفحات HTML برنامه‌های MCP
server/discover متد کنونی RPC برای شناسایی پروتکل و قابلیت‌ها
resources/list متد الزامی دریافت لیست منابع، زمانی که سرور منابع را معرفی می‌کند
resultType تفکیک‌کننده‌ی الزامی برای نتایج موفق در نسخه‌های جدید
ui/initialize اولین پیام رابط Apps، مجزا از فرایند اولیه‌سازی حذف‌شده در هسته
ui/notifications/initialized اعلان آمادگی نمای برنامه که پس از پاسخ میزبان ارسال می‌شود
CSP سیاست مرورگر برای محدودسازی اسکریپت‌ها، استایل‌ها، تصاویر و مبدأهای شبکه
حالت بازگشت به متن (Text fallback) رفتار پشتیبان ابزار برای پاسخ به میزبان‌هایی که از Apps پشتیبانی نمی‌کنند

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

خودآزمایی

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

  1. پیش از درسیک برنامه نوین MCP کجا باید منبع رابط کاربری (UI) خود را به یک ابزار پیوند دهد؟
  2. حین درسیک کلاینت بر اساس نگارش 2026-07-28 چگونه پشتیبانی از برنامه‌های MCP را اعلان می‌کند؟
  3. حین درسمتد ui/initialize چه چیزی را مقداردهی اولیه می‌کند؟
  4. حین درسکدام پاسخ منبع برای یک برنامه بسته‌بندی‌شده و مستقل از کاربر امن‌ترین است؟
  5. پس از درسیک هاست بدنه resources/read را برای یک URI اما با هدر Mcp-Name متفاوتی دریافت می‌کند. چه رخدادی باید پیش بیاید؟
  6. پس از درسحالت جایگزین (fallback) صحیح برای هاستی که از برنامه‌های MCP پشتیبانی نمی‌کند چیست؟

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

آیا متد ui/initialize در Apps همان نشست قبلی پروتکل MCP را ایجاد می‌کند؟

خیر؛ متد ui/initialize صرفاً یک ساختار JSON-RPC روی بستری محلی مثل postMessage بین فریم میزبان و iframe است و نشست انتقالی جدیدی در هسته پروتکل MCP نمی‌سازد.

اگر کلاینتی از افزونه MCP Apps پشتیبانی نکند چه اتفاقی می‌افتد؟

سرور باید رفتار بازگشت به متن (text fallback) را حفظ کند؛ یعنی ابزار را بدون فراداده _meta.ui بازگرداند و خروجی متنی کاربردی برای tools/call ارائه دهد.

سرور در نسخه بدون وضعیت چگونه افزونه MCP Apps را اعلام می‌کند؟

سرور از طریق متد server/discover شناسه io.modelcontextprotocol/ui را معرفی می‌کند و کلاینت نیز در params._meta هر درخواست، قابلیت‌هایش را می‌فرستد.

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

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