فاز 13 · درس 13

افزونه‌ی Tasks در MCP: اجرای کارهای ماندگار روی هسته‌ی بی‌وضعیت

نوع
ساختنی
زمان
حدود 90 دقیقه
زبان‌ها
Python
پیش‌نیاز
فاز 13 · 09 (بسترهای انتقال)، فاز 13 · 11 (MRTR بی‌وضعیت)، فاز 13 · 12 (استخراج اطلاعات)
یک گیره فلزی محکم که برچسب شناسایی یک فرآیند معلق را روی تخته‌ای بادوام نگه داشته است در کنار نشانگرهای وضعیت در حال پیشرفت
فهرست مطالب
  1. اهداف یادگیری
  2. چرا تسک‌ها به‌شکل افزونه ارائه شده‌اند؟
  3. هسته‌ی بی‌وضعیت، برنامه‌ی باوضعیت
  4. توافق بر سر قابلیت‌ها
  5. ایجاد تسک با هدایت سرور
  6. ساختار تسک
  7. نظرسنجی دوره‌ای با tasks/get
  8. دریافت ورودی حین اجرای تسک
  9. نیاز به ورودی پیش از ایجاد تسک
  10. نیاز به ورودی پس از ایجاد تسک
  11. لغو هماهنگ عملیات
  12. اعلانات اختیاری
  13. رفتارشناسی خطاها
  14. خطای پروتکل
  15. نتیجه‌ی اجرای تسک
  16. ماندگاری، انقضا و مالکیت
  17. پیاده‌سازی
  18. استفاده‌ی عملی
  19. آماده‌سازی نهایی
  20. تمرین‌ها
  21. اصطلاحات کلیدی
  22. سازگاری با نسخه‌های قدیمی
  23. برای مطالعه‌ی بیشتر

در پروتکل MCP (نسخه‌ی 2026-07-28) تمام سشن‌ها، هدر Mcp-Session-Id و متدهای متصل به نشست حذف شده‌اند و بستر ارتباطی کاملاً بی‌وضعیت (stateless) عمل می‌کند. با این حال در دنیای واقعی، بسیاری از ابزارها مانند گزارش‌گیری‌های سنگین، تولید داده‌های حجیم یا کارهایی که در میانه به تأیید کاربر نیاز دارند، نمی‌توانند در قالب مهلت زمانی یک درخواست استاندارد HTTP یا stdio تمام شوند.

افزونه‌ی رسمی io.modelcontextprotocol/tasks این چالش را بدون بازگرداندن سشن به هسته‌ی پروتکل حل می‌کند. در این رویکرد، سرور یک دستگیره‌ی ماندگار (task id) تولید می‌کند که روی یک ذخیره‌ساز مشترک ذخیره می‌شود؛ بنابراین کلاینت می‌تواند حتی پس از ری‌استارت شدن، وضعیت تسک را از هر نمونه‌ی سرور استعلام کرده یا ورودی‌های تکمیلی را ارسال کند.

در این درس یاد می‌گیرید که چگونه چرخه حیات کارهای طولانی‌مدت را با استفاده از متدهای tasks/get، tasks/update و tasks/cancel پیاده کنید، متدهای منسوخ قدیمی را کنار بگذارید و سیستم را در برابر ناپایداری‌های شبکه و ری‌استارت سرویس‌ها مقاوم بسازید.

پروتکل MCP بی‌وضعیت (Stateless) است، اما این به معنای پایان اجباری تمام عملیات در قالب یک درخواست نیست. افزونه‌ی رسمی Tasks دستگیره‌ای (handle) ماندگار و صریح برای کارهای طولانی‌مدت فراهم می‌کند. سرور می‌تواند این دستگیره را در پاسخ tools/call برگرداند، هر نمونه‌ای از سرور می‌تواند به tasks/get پاسخ دهد و ورودی کلاینت نیز از طریق tasks/update دریافت می‌شود، بدون اینکه نیازی به زنده کردن سشن‌های پروتکل باشد.

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

  • تشخیص تفاوت بستر انتقال پروتکل بی‌وضعیت از وضعیت ماندگار تسک در سطح برنامه.
  • توافق بر سر قابلیت افزونه‌ی io.modelcontextprotocol/tasks در قابلیت‌های هر درخواست و server/discover.
  • بازگرداندن CreateTaskResult هدایت‌شده از سمت سرور با resultType: "task" تنها پس از ایجاد ماندگار تسک.
  • نظرسنجی دوره‌ای (Poll) با tasks/get، تأمین ورودی تسک با tasks/update و درخواست لغو هماهنگ (cooperative cancellation) با tasks/cancel.
  • کنار گذاشتن فرضیات قدیمی درباره‌ی متدهای tasks/status، tasks/result و tasks/list.
  • عضویت در اعلانات اختیاری تسک از طریق subscriptions/listen روی جریان SSE در پاسخ POST.
  • مدل‌سازی صحیح انقضای تسک، بازیابی پس از راه‌اندازی مجدد، حذف داده‌های تکراری در کلیدهای ورودی و خطاهای اجرایی.

چرا تسک‌ها به‌شکل افزونه ارائه شده‌اند؟

تسک‌ها نخستین بار در تاریخ 2025-11-25 به‌عنوان قابلیتی آزمایشی در هسته‌ی پروتکل ظاهر شدند. در بازطراحی جولای 2026، آن‌ها به افزونه‌ی رسمی io.modelcontextprotocol/tasks منتقل شدند تا کلاینت‌ها و سرورها بتوانند این چرخه‌ی حیات مازاد را در صورت تمایل انتخاب کنند، بدون اینکه هسته‌ی پروتکل برای همه سنگین‌تر شود.

مشخصات این افزونه همچنان در وضعیت پیش‌نویس (draft) قرار دارد، هرچند اکنون مرجع رسمی تسک‌ها به شمار می‌رود. نسخه‌ی افزونه‌ی موردپشتیبانی در SDK خود را قفل (pin) کنید، سناریوهای انطباق را اجرا کنید و تطبیق‌دهنده‌های ارتباطی (wire adapters) را از دامنه‌ی ذخیره‌سازی و ورکرها جدا نگه دارید.

تنها زمانی از تسک استفاده کنید که عملیات دست‌کم یکی از ویژگی‌های زیر را داشته باشد:

  • زمان اجرای آن ممکن است از مهلت مجاز یک درخواست معمولی (timeout) فراتر رود.
  • صف ورکر (worker queue) یا سیستم جاب خارجی مدیریت اجرا را بر عهده دارد.
  • کلاینت باید بتواند پس از ری‌استارت خود، کار را بازیابی کند.
  • عملیات حین اجرا منتظر دریافت ورودی از کاربر یا مدل متوقف می‌شود.
  • لغو عملیات و دریافت ماندگار نتیجه از الزامات محصول است.

برای یک جست‌وجوی قطعی و سبک تسک نسازید. دستگیره، ماندگاری، نظرسنجی دوره‌ای، انقضا و لغو، پیچیدگی واقعی به همراه دارند.

هسته‌ی بی‌وضعیت، برنامه‌ی باوضعیت

نسخه‌ی 2026-07-28 پروتکل MCP متدهای initialize، notifications/initialized، سشن‌های پروتکل و هدر Mcp-Session-Id را حذف کرده است. این موضوع مانع ساخت محصولات باوضعیت نمی‌شود.

شناسه‌ی تسک (task id) یک وضعیت صریح در سطح برنامه است:

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

این ساختار از نظر عملیاتی با وضعیت پنهان متصل به یک کانکشن کاملاً تفاوت دارد.

این چهار چرخه‌ی حیات را کاملاً تفکیک‌شده نگه دارید:

وضعیت طول عمر محل نگهداری
فراداده‌ی پروتکل یک درخواست params._meta، که در هر فراخوانی مجدداً اعتبارسنجی می‌شود
پردازش بستر انتقال یک درخواست stdio یا پاسخ HTTP هماهنگ‌کننده‌ی در حال اجرا با ضرب‌الاجل مشخص
ادامه‌ی MRTR یک توالی تلاش مجدد پارامتر requestState حفاظت‌شده از نظر یکپارچگی، همراه با کنترل‌های بازپخش در صورت نیاز
تسک ماندگار در میان درخواست‌ها، نمونه‌های سرور، ری‌استارت‌ها و اتصالات مجدد ذخیره‌ساز مشترک برنامه با کلید شناسه‌ی مجاز taskId

نگهداری داده‌های تسک در حافظه‌ی پردازه (process memory) به معنای باوضعیت شدن MCP نیست، بلکه برنامه را غیرقابل‌اعتماد می‌کند. پروتکل بی‌وضعیت باقی می‌ماند، اما فراخوانی بعدی tasks/get که به نمونه‌ی دیگری هدایت شود نمی‌تواند اطلاعات را پیدا کند. اطلاعات را پیش از تحویل دستگیره ذخیره کنید، سپس طوری پیاده‌سازی کنید که تمام متدهای تسک با بررسی‌های امنیتی مستاجر (tenant) و هویت اصلی (principal)، به همان رکورد مشترک دسترسی یابند.

توافق بر سر قابلیت‌ها

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

{
  "_meta": {
    "io.modelcontextprotocol/protocolVersion": "2026-07-28",
    "io.modelcontextprotocol/clientCapabilities": {
      "extensions": {
        "io.modelcontextprotocol/tasks": {}
      }
    },
    "io.modelcontextprotocol/clientInfo": {
      "name": "lesson-client",
      "version": "1.0.0"
    }
  }
}

سرور مقادیر دقیق supportedVersions، قابلیت‌ها، ttlMs و cacheScope را از server/discover برمی‌گرداند و همان افزونه را ذیل capabilities قرار می‌دهد. چون ابزارها را اعلام کرده، پیاده‌سازی متد اجباری tools/list نیز الزامی است. این خروجی شامل توصیف‌گر قطعی generate_report، ساختار شیء معتبر inputSchema، مقدار resultType: "complete"، فراداده‌ی هویت سرور و راهنماهای کش عمومی (public cache hints) است.

اگر کلاینتی افزونه را اعلام نکرده باشد و متد تسک را فراخوانی کند، خطای 32021- (Missing Required Client Capability) را همراه با مقدار data.requiredCapabilities به‌شکل {"extensions":{"io.modelcontextprotocol/tasks":{}}} دریافت می‌کند. ارسال رشته‌ی پروتکل پشتیبانی‌نشده خطای 32022- با داده‌های دقیق supported و requested برمی‌گرداند؛ نسخه‌ی ناموجود یا غیررشته‌ای خطای 32602- می‌دهد.

پیامی بدون id در JSON-RPC یک اعلان (notification) محسوب می‌شود. گیرنده می‌تواند آن را پردازش کند، اما هیچ پاسخ یا خطایی در JSON-RPC صادر نمی‌کند. تطبیق‌دهنده‌ی Streamable HTTP برای اعلان پذیرفته‌شده پاسخ 202 Accepted بدون بدنه برمی‌گرداند.

در حال حاضر فقط tools/call از اجرای مبتنی بر افزایش داده با تسک پشتیبانی می‌کند. انتزاع داخلی‌تان را طوری طراحی کنید که اضافه‌شدن انواع درخواست‌های جدید در آینده نیازی به بازنویسی بخش ذخیره‌سازی نداشته باشد.

ایجاد تسک با هدایت سرور

فلگ قدیمی کلاینت یعنی params._meta.task.required حذف شده است. اکنون کلاینت پشتیبانی از افزونه را اعلام می‌کند، سپس سرور تصمیم می‌گیرد که آیا یک tools/call خاص به تسک تبدیل شود یا خیر.

درخواست:

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "generate_report",
    "arguments": {"size": "large"},
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {}
        }
      }
    }
  }
}

پاسخ:

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "task",
    "taskId": "tsk_786512e29e0d",
    "status": "working",
    "statusMessage": "Preparing report outline.",
    "createdAt": "2026-08-21T10:30:00Z",
    "lastUpdatedAt": "2026-08-21T10:30:00Z",
    "ttlMs": 900000,
    "pollIntervalMs": 1000
  }
}

سرور نباید این دستگیره را قبل از اینکه شناسه در tasks/get قابل‌بازیابی باشد برگرداند. در پایگاه‌های داده با سازگاری نهایی (eventual consistency)، پیش از پاسخ دادن منتظر قابل‌مشاهده شدن خواندن بمانید؛ در غیر این صورت ممکن است کلاینت شناسه‌ای ظاهراً معتبر دریافت کند و بلافاصله با خطای «یافت نشد» مواجه شود.

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

ساختار تسک

هر تسک شامل این موارد است:

  • مقدار taskId: شناسه‌ی پایدار تولیدشده توسط سرور؛
  • مقدار status: یکی از وضعیت‌های working، input_required، completed، cancelled یا failed؛
  • مقادیر createdAt و lastUpdatedAt: برچسب‌های زمانی با استاندارد ISO 8601؛
  • مقدار ttlMs: مدت‌زمان انقضا از زمان ایجاد، یا null در صورتی که محدودیتی اعلام نشده باشد؛
  • فیلد اختیاری pollIntervalMs: حداقل فاصله‌ی زمانی پیشنهادی سرور برای نظرسنجی دوره‌ای؛
  • فیلد اختیاری statusMessage: توضیحات مناسب برای نمایش به کاربر یا مدل.

فیلدهای مربوط به هر وضعیت تنها در زمان لازم ظاهر می‌شوند:

  • وضعیت input_required شامل inputRequests است.
  • وضعیت completed ساختار result درخواست اصلی را در بر دارد.
  • وضعیت failed حاوی شیء error در استاندارد JSON-RPC است.

کلاینت باید pollIntervalMs را رعایت کند. سرور ممکن است درخواست‌های مکررتر را محدود (rate-limit) کند یا این فاصله را در طول حیات تسک تغییر دهد.

نظرسنجی دوره‌ای با tasks/get

کلاینت اسنپ‌شات فعلی را درخواست می‌کند:

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tasks/get
Mcp-Name: tsk_786512e29e0d
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tasks/get",
  "params": {
    "taskId": "tsk_786512e29e0d",
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {}
        }
      }
    }
  }
}

خود فراخوانی tasks/get کامل شده، بنابراین فیلد نتیجه‌ی آن همیشه resultType: "complete" است. با این حال، وضعیت تسک داخلی می‌تواند همچنان status: "working" یا status: "input_required" باشد.

این تفکیک مانع یک باگ رایج در پارسرها می‌شود:

result.resultType = complete    means the tasks/get RPC finished
result.status = working        means the represented job is still running

چیزی به‌نام فراخوانی tasks/result وجود ندارد. با پایان کار تسک، پاسخ tasks/get بعدی خروجی اصلی CallToolResult را درون فیلد result به‌صورت درون‌خطی قرار می‌دهد:

{
  "resultType": "complete",
  "taskId": "tsk_786512e29e0d",
  "status": "completed",
  "createdAt": "2026-08-21T10:30:00Z",
  "lastUpdatedAt": "2026-08-21T10:34:12Z",
  "ttlMs": 900000,
  "result": {
    "resultType": "complete",
    "content": [
      {"type": "text", "text": "Generated large report with approved outline."}
    ],
    "structuredContent": {"size": "large", "approved": true},
    "isError": false,
    "_meta": {
      "io.modelcontextprotocol/serverInfo": {
        "name": "tasks-demo",
        "version": "1.0.0"
      }
    }
  },
  "_meta": {
    "io.modelcontextprotocol/serverInfo": {
      "name": "tasks-demo",
      "version": "1.0.0"
    }
  }
}

مقدار resultType بیرونی نشان می‌دهد که RPC مربوط به tasks/get کامل شده است. مقدار تفکیک‌کننده‌ی درون result.resultType نیز بیانگر تکمیل فراخوانی ابزار اصلی است. وجود این فیلد داخلی الزامی است. همچنین شیء درونی CallToolResult باید فراداده‌ی io.modelcontextprotocol/serverInfo اختصاصی خود را داشته باشد؛ در این درس به‌جای ذخیره‌ی داده‌ی بدون‌نوع، این متادیتا را نیز نگهداری می‌کنیم.

متد tasks/list وجود ندارد. سرورهای بدون سشن نمی‌توانند با اطمینان تشخیص دهند چه تسک‌هایی متعلق به فهرستی در دامنه‌ی یک کانکشن خاص هستند. برنامه‌هایی که به تاریخچه نیاز دارند باید ابزاری در سطح دامنه‌ی خود پیاده‌سازی کنند که فیلترها و قوانین مالکیت صریحی داشته باشد.

دریافت ورودی حین اجرای تسک

دریافت ورودی در تسک و الگوی هسته‌ی MRTR شبیه به هم هستند اما از روش‌های ادامه‌ی متفاوتی استفاده می‌کنند.

نیاز به ورودی پیش از ایجاد تسک

از همان tools/call اولیه، پاسخ resultType: "input_required" مربوط به هسته را برگردانید. کلاینت داده را پر می‌کند و همان فراخوانی اصلی را دوباره می‌فرستد. تنها پس از پایان این نوبت‌های همگام MRTR، تسک را ایجاد کنید.

نیاز به ورودی پس از ایجاد تسک

وضعیت تسک را به input_required تغییر دهید. متد tasks/get موارد inputRequests باز را اعلام می‌کند و کلاینت پاسخ‌ها را از طریق tasks/update می‌فرستد. کلاینت دیگر درخواست اولیه‌ی tools/call را تکرار نمی‌کند.

اسنپ‌شات:

{
  "resultType": "complete",
  "taskId": "tsk_786512e29e0d",
  "status": "input_required",
  "createdAt": "2026-08-21T10:30:00Z",
  "lastUpdatedAt": "2026-08-21T10:31:00Z",
  "ttlMs": 900000,
  "inputRequests": {
    "approve_outline": {
      "method": "elicitation/create",
      "params": {
        "mode": "form",
        "message": "Approve the generated report outline?",
        "requestedSchema": {
          "type": "object",
          "properties": {"approved": {"type": "boolean"}},
          "required": ["approved"]
        }
      }
    }
  }
}

به‌روزرسانی:

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tasks/update
Mcp-Name: tsk_786512e29e0d
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "tasks/update",
  "params": {
    "taskId": "tsk_786512e29e0d",
    "inputResponses": {
      "approve_outline": {
        "action": "accept",
        "content": {"approved": true}
      }
    },
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {}
        }
      }
    }
  }
}

پاسخ موفقیت‌آمیز شامل یک تأییدیه‌ی خالی به‌همراه resultType: "complete" است. از آنجا که تغییر وضعیت ممکن است با تاخیر اعمال شود (eventually consistent)، کلاینت به نظرسنجی دوره‌ای یا گوش دادن ادامه می‌دهد.

هر کلید در inputRequests باید در کل طول حیات تسک یکتا باشد. اسنپ‌شات‌های پیاپی tasks/get ممکن است همان کلید باز را نشان دهند؛ کلاینت‌ها موارد تکراری را در رابط کاربری فیلتر می‌کنند و سرورها پاسخ‌های ارائه‌شده برای کلیدهای نامعتبر، منسوخ یا قبلاً تأمین‌شده را نادیده می‌گیرند. اگر به‌روزرسانی ناقص باشد، تسک تا زمان پاسخ به همه‌ی کلیدهای لازم در حالت input_required باقی می‌ماند.

لغو هماهنگ عملیات

متد tasks/cancel نیت لغو را اعلام کرده و پاسخی خالی مبنی بر تکمیل عملیات برمی‌گرداند. این تأییدیه تضمین نمی‌کند که ورکر کار خود را متوقف کرده باشد؛ ممکن است پردازش زودتر تمام شود، درخواست لغو نادیده گرفته شود یا تغییر وضعیت دیرتر رخ دهد.

POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tasks/cancel
Mcp-Name: tsk_786512e29e0d
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tasks/cancel",
  "params": {
    "taskId": "tsk_786512e29e0d",
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {}
        }
      }
    }
  }
}

در هر سه متد تسک، مقدار هدر Mcp-Name با params.taskId برابر است و نام متد JSON-RPC را تکرار نمی‌کند. در code/main.py این منطق درون تابع make_http_request متمرکز شده است.

ورکر این درس لغو را بلافاصله اعمال می‌کند، بنابراین فراخوانی‌های مکرر تأثیری مشابه دارند (idempotent). اما در کلاینت محیط عملیاتی باید لغو را همواره هماهنگ در نظر گرفت، نه اینکه پس از دریافت تأییدیه وضعیت نهایی تسک را قطعی دانست.

از notifications/cancelled برای لغو تسک استفاده نکنید. آن اعلان برای لغو درخواست‌ها است، نه تسک‌های ماندگار.

این تفاوت در مرز مسیریابی اهمیت بالایی دارد. لغو درخواست فقط یک عملیات در حال پردازش JSON-RPC یا پاسخ HTTP در دامنه‌ی درخواست را هدف می‌گیرد. اگر tools/call قبلاً resultType: "task" را برگردانده باشد، کار آن درخواست تمام شده است و بستن بستر انتقال نمی‌تواند کار ماندگار را شناسایی یا متوقف کند. متد tasks/cancel یک فراخوانی RPC جدید و احرازشده است که با ارائه‌ی params.taskId، قرار دادن همان شناسه در Mcp-Name، یافتن بک‌اند صاحب تسک و ثبت نیت لغو هماهنگ، تأییدیه برمی‌گرداند بدون اینکه ادعا کند کار متوقف شده است.

بنابراین، گیت‌وی باید هماهنگ‌کننده‌های درخواست و مسیرهای تسک را در جدول‌های جداگانه‌ای نگه دارد. جدول درخواست‌ها پس از پایان پاسخ پاک می‌شود، اما مسیر تسک باید تا رسیدن به وضعیت نهایی و سررسید انقضا پابرجا بماند. در درس 29: قابلیت اطمینان، لغو و کنترل جریان در MCP، قوانین رقابت در پردازش (race conditions)، مهلت زمانی، تکرارپذیری، پس‌زدگی (backpressure) و تلاش مجدد برای هر دو مسیر تشریح شده است.

اعلانات اختیاری

نظرسنجی دوره‌ای خط مبنا است. کلاینتی که به‌روزرسانی‌های push می‌خواهد، متد subscriptions/listen را همراه با شناسه‌های تسک ارسال می‌کند. در پروتکل Streamable HTTP، این کار با یک درخواست POST انجام می‌شود که پاسخ آن یک جریان SSE در دامنه‌ی همان درخواست است. جریان رویداد مستقل از نوع GET یا سشن پروتکل برای فعال نگه‌داشتن وجود ندارد.

سرور شناسه‌های پذیرفته‌شده را با اعلان notifications/subscriptions/acknowledged تأیید می‌کند و پس از آن می‌تواند اسنپ‌شات‌های کامل را از طریق notifications/tasks بفرستد. این تأییدیه و تمامی اعلانات تسک، فیلد io.modelcontextprotocol/subscriptionId را در بخش _meta و برابر با شناسه‌ی درخواست subscriptions/listen قرار می‌دهند. سایر محتویات اعلان تسک دقیقاً معادل خروجی tasks/get در همان لحظه است.

کلاینت‌ها همچنان موظف‌اند افزونه‌ی Tasks را اعلام کنند. همچنین باید بتوانند با تکیه بر شناسه‌ی ماندگار تسک‌ها اتصال خود را مجدداً برقرار کنند، نه اینکه به بازپخش رویدادها یا هدر Last-Event-ID وابسته باشند.

رفتارشناسی خطاها

دو لایه‌ی خطا را درست به کار ببرید:

خطای پروتکل

پارامترهای متد نامعتبر یا شناسه‌ی تسک ناشناخته خطای JSON-RPC برمی‌گردانند که معمولاً 32602- است. اعلام نکردن افزونه خطای 32021- همراه با شیء قابلیت‌های الزامی را به دنبال دارد.

نتیجه‌ی اجرای تسک

  • نتیجه‌ی عادی ابزار که فیلد isError: true دارد همچنان یک تسک completed محسوب می‌شود، زیرا فراخوانی ابزار به نتیجه‌ی تعریف‌شده‌ی خود رسیده است.
  • خطای JSON-RPC هنگام اجرای معوق، تسک را به failed تبدیل کرده و همان خطای JSON-RPC را در فیلد error ذخیره می‌کند.
  • رد درخواست توسط کاربر می‌تواند به وضعیت cancelled، نتیجه‌ی تکمیل‌شده‌ی حاوی پیام انصراف یا خروجی امن دیگری متناسب با منطق برنامه ختم شود. این تصمیم را مستند کنید.

ماندگاری، انقضا و مالکیت

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

کلید ذخیره‌سازی باید شامل اطلاعات مستاجر و هویت اصلی معتبر باشد یا بتواند آن‌ها را مشخص کند. دانستن شناسه‌ی تسک نباید مجوز دسترسی به آن تلقی شود. در هر بار اجرای tasks/get، tasks/update، tasks/cancel و ایجاد عضویت، مالکیت را بررسی کنید.

مقدار ttlMs از زمان ایجاد تسک محاسبه می‌شود و ممکن است تغییر کند. کلاینت می‌تواند از آن به‌عنوان مرزی برای تشخیص تسک‌هایی که دیگر به‌روزرسانی تولید نمی‌کنند استفاده کند. سرور می‌تواند تسک منقضی‌شده را ناموفق علامت زده و بعداً آن را حذف کند. این مقدار را تعهدی برای نگهداری نتیجه‌ی تسک تکمیل‌شده به همان میزان میلی‌ثانیه پس از تکمیل تلقی نکنید.

عملیات نوشتن را اتمیک (atomic) یا در قالب تراکنش انجام دهید. در کد این درس ابتدا یک فایل موقت نوشته شده و سپس به‌صورت اتمیک تغییر نام پیدا می‌کند. یک سرویس با چند نمونه‌ی هم‌زمان (multi-replica) باید از ذخیره‌ساز مشترک ماندگار و اجاره‌ی ورکر (worker lease) یا روش‌های هماهنگی مشابه استفاده کند.

وظیفه ناهمگاماکنون فراخوانی کن، بعداً دریافت نما
ASYNC TASKThe official tasks extension lets long-running work return a task handle instead of holding the request open. The client polls with tasks/get; a terminal task contains its final result. If the task enters input_required, tasks/update supplies responses to outstanding inputRequests, and tasks/cancel signals cancellation intent. The current extension has no tasks/list or tasks/result method.workinginput_requiredcompletedfailedبررسی با tasks/get؛ ارسال ورودی‌های خواسته‌شده با tasks/update
اکستنشن رسمی وظایف (tasks) این امکان را فراهم می‌کند که کارهای طولانی‌مدت به جای باز نگه داشتن درخواست، یک شناسه وظیفه برگردانند. کلاینت با tasks/get وضعیت را بررسی می‌کند؛ یک وظیفه در وضعیت نهایی، نتیجه قطعی خود را در بر دارد. اگر وظیفه وارد input_required شود، tasks/update پاسخ‌های مربوط به inputRequests معلق را تأمین می‌کند و tasks/cancel نیت لغو را اعلام می‌نماید. اکستنشن فعلی متد tasks/list یا tasks/result ندارد.

پیاده‌سازی

فایل code/main.py یک سرویس تسک قطعی را پیاده‌سازی می‌کند:

  • متد server/discover فیلدهای supportedVersions، راهنماهای کش و افزونه‌ی Tasks را برمی‌گرداند.
  • متد tools/list توصیف‌گر قطعی و قابل کش generate_report را با اسکیمای ورودی معتبر برمی‌گرداند.
  • متد tools/call تسک را پیش از بازگرداندن resultType: "task" ایجاد و ماندگار می‌کند.
  • نمونه‌ی جدیدی از سرویس همان تسک را بارگذاری می‌کند تا بازیابی پس از ری‌استارت را نشان دهد.
  • متد tasks/get اسنپ‌شات‌های کامل تسک را برمی‌گرداند.
  • ورکر وضعیت را از working به input_required تغییر می‌دهد.
  • متد tasks/update پاسخ فرم را دریافت کرده و تأییدیه‌ی کامل و خالی برمی‌گرداند.
  • ورکر ساختار تودرتوی CallToolResult را همراه با resultType اختصاصی و هویت سرور ذخیره کرده و به completed می‌رود.
  • متد tasks/cancel در این پیاده‌سازی idempotent است.
  • سازنده‌ی درخواست HTTP مقدار هدر Mcp-Name را برای متدهای tasks/get، tasks/update و tasks/cancel برابر با params.taskId قرار می‌دهد.
  • توابع کمکی اعلانات از notifications/subscriptions/acknowledged و notifications/tasks استفاده می‌کنند و هر دو را با شناسه‌ی درخواست گوش‌دادن برچسب می‌زنند.
  • اعلان‌های فاقد شناسه، هیچ پاسخی در فرمت JSON-RPC تولید نمی‌کنند.

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

استفاده‌ی عملی

از ریشه‌ی مخزن اجرا کنید:

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

توالی خروجی مورد انتظار:

id=0 resultType=complete status=ack
id=1 resultType=task status=working
id=2 resultType=complete status=working
id=3 resultType=complete status=input_required
id=4 resultType=complete status=ack
id=5 resultType=complete status=completed

همچنین بررسی کنید که متدهای tasks/status، tasks/result و tasks/list در سرویس امروزی خطای method-not-found برگردانند. مطمئن شوید خروجی tools/list قطعی است و تمام متدهای کنونی تسک در پروتکل HTTP شناسه‌ی تسک را در هدر Mcp-Name منعکس می‌کنند.

آماده‌سازی نهایی

فایل outputs/skill-task-store-designer.md اکنون حاوی طراحی منطبق بر این افزونه است: توافق بر سر قابلیت‌ها، ماندگاری پیش از بازگرداندن شناسه، متدهای روزآمد، جریان به‌روزرسانی ورودی، مالکیت، انقضا، لغو، عضویت در اعلانات و مسیر مهاجرت از متدهای آزمایشی قدیمی.

تمرین‌ها

  1. یک کلید ورودی باز دیگر به تسک اضافه کنید. با ارسال یک tasks/update ناقص نشان دهید که تسک تا زمان تکمیل هر دو کلید در وضعیت input_required باقی می‌ماند.
  2. تفکیک مستاجرها را به ذخیره‌ساز اضافه کنید و در صورت ارائه‌ی شناسه‌ی تسک معتبر توسط هویت اصلی احرازشده‌ی نادرست، آن را رد کنید.
  3. یک worker lease منقضی‌شونده اضافه کنید. نشان دهید که دو نمونه از سرویس نمی‌توانند یک تسک مشترک را به‌طور هم‌زمان تکمیل کنند.
  4. یک تطبیق‌دهنده‌ی SSE بر پایه‌ی پاسخ POST برای متد subscriptions/listen پیاده کنید؛ بدون استفاده از GET، هدر Last-Event-ID یا هدر سشن.
  5. روال پاک‌سازی تسک‌های منقضی را پیاده کنید؛ به‌نحوی‌که تسک منقضی‌شده از شناسه‌ی تسک نامعتبر تشخیص داده شود، بدون اینکه اطلاعاتی از وجود تسک‌های مستاجران دیگر به بیرون درز کند.

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

اصطلاح مفهوم در افزونه‌ی فعلی
افزونه‌ی Tasks قابلیت اختیاری io.modelcontextprotocol/tasks برای کارهای ماندگار ناهمگام
CreateTaskResult پاسخ با resultType: "task" هدایت‌شده از سمت سرور به یک درخواست واجد شرایط
tasks/get نظرسنجی دوره‌ای برای اسنپ‌شات کامل فعلی تسک، شامل نتیجه‌ی نهایی یا ورودی‌های در انتظار
tasks/update ارسال پاسخ برای موارد inputRequests تکمیل‌نشده در تسک
tasks/cancel اعلام دریافت نیت لغو هماهنگ عملیات
input_required وضعیت تسک که نشان می‌دهد پردازش منتظر ورودی از سمت کلاینت است
pollIntervalMs حداقل زمان پیشنهادی سرور پیش از اجرای نظرسنجی دوره‌ای بعدی
ttlMs مدت‌زمان اعتبار تسک که از لحظه‌ی ایجاد سنجیده می‌شود
ماندگاری پیش از بازگرداندن الزام به ذخیره شدن شناسه‌ی تسک پیش از ارسال دستگیره‌ی آن به کلاینت
notifications/tasks اسنپ‌شات کامل اختیاری از تسک که در جریان SSE عضویت ارسال می‌شود

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

طراحی آزمایشی 2025-11-25 از سیستم افزایش داده با تسک به درخواست کلاینت و متدهای tasks/status، tasks/result و متد اختیاری tasks/list استفاده می‌کرد. این نام‌ها را فقط درون یک تطبیق‌دهنده‌ی قدیمی و ایزوله‌شده نگه دارید. کلاینت کنونی از قابلیت افزونه استفاده می‌کند، دستگیره‌های هدایت‌شده توسط سرور را می‌پذیرد، با tasks/get نظرسنجی دوره‌ای انجام می‌دهد، ورودی را با tasks/update می‌فرستد و نتیجه‌ی نهایی را مستقیماً از اسنپ‌شات تسک می‌خواند.

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

خودآزمایی

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

  1. پیش از درسچگونه یک سرور MCP بی‌وضعیت می‌تواند از کاری که ده دقیقه طول می‌کشد پشتیبانی کند؟
  2. حین درسسرور چه زمانی مجاز است resultType برابر با task را از tools/call برگرداند؟
  3. حین درسپاسخ tasks/get دارای resultType برابر با complete و status برابر با working است. این به چه معناست؟
  4. حین درسیک کلاینت چگونه پس از ایجاد وظیفه به وضعیت input_required پاسخ می‌دهد؟
  5. پس از درسپاسخ موفق tasks/cancel چه چیزی را تضمین می‌کند؟
  6. پس از درسکدام مجموعه متدها با اکستنشن رسمی کنونی Tasks مطابقت دارد؟

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

آیا برای دریافت نتیجه‌ی نهایی تسک متدی مثل tasks/result وجود دارد؟

خیر؛ در مشخصات جدید، متدهای tasks/result و tasks/status حذف شده‌اند. نتیجه‌ی نهایی در قالب شیء CallToolResult مستقیماً درون اسنپ‌شاتی قرار می‌گیرد که با متد tasks/get فراخوانی می‌شود.

تفاوت لغو تسک با tasks/cancel و notifications/cancelled چیست؟

اعلان notifications/cancelled مختص متوقف کردن یک درخواست موقت JSON-RPC در سطح بستر انتقال است، در حالی که tasks/cancel یک فراخوانی احرازشده برای اعلام نیت لغو هماهنگ روی یک تسک ماندگار در لایه‌ی برنامه است.

اصل «ماندگاری پیش از بازگرداندن» (Durable-before-return) به چه معناست؟

سرور نباید پاسخ tools/call حاوی taskId را به کلاینت بفرستد مگر اینکه داده‌های تسک در ذخیره‌ساز مشترک نوشته شده و در فراخوانی بلافاصله‌ی tasks/get قابل خواندن باشند.

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

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