---
title: 'إضافة MCP Tasks: خادم TypeScript يعمل (2026)'
description: تحدد إضافة MCP Tasks ثلاث طرق لاستدعاءات الأدوات غير المتزامنة، ولكن الـ SDK الرسمي لا يزال يرسل هيكلاً أقدم. إليكم خادم تم اختباره وينفذ ذلك بشكل صحيح.
url: https://nerdleveltech.com/ar/mcp-tasks-extension-tutorial
language: ar
date: '2026-09-18'
category: ai-ml
tags:
  - mcp
  - model context protocol
  - tasks extension
  - typescript
  - async tool calls
  - mcp sdk
  - agent tooling
markdown_url: https://nerdleveltech.com/ar/mcp-tasks-extension-tutorial.md
attribution: 'Source: NerdLevelTech — https://nerdleveltech.com/ar/mcp-tasks-extension-tutorial'
terms: https://nerdleveltech.com/llms.txt
---

# إضافة MCP Tasks: خادم TypeScript يعمل (2026)

تسمح إضافة MCP Tasks (`io.modelcontextprotocol/tasks`)، التي تم إقرارها في مواصفات 2026-07-28، باستدعاء الأداة لإرجاع مقبض (handle) مستدام بدلاً من حظر التنفيذ: حيث تقوم العملاء بعمل poll لـ `tasks/get`، والإجابة على المدخلات في منتصف المهمة عبر `tasks/update`، والإلغاء باستخدام `tasks/cancel`[^1][^2]. لا توجد حزمة SDK رسمية من TypeScript تطبق هذا الشكل بدقة حتى الآن.

هذه الفجوة ليست واضحة من الوثائق. حتى وقت كتابة هذا المقال، لا يزال دعم المهام المدمج في `@modelcontextprotocol/sdk` يتحدث بإصدار قديم سابق للإقرار، وحزم v2 التي تم تقسيمها حديثاً لا تطبق Tasks على الإطلاق في مفردات النقل النشطة الخاصة بها — بل تحتفظ فقط بهذا الإصدار القديم نفسه كأنواع ميتة غير تشغيلية (non-runtime types). يقوم هذا المنشور ببناء خادم وفقاً للمواصفات المقرة يدوياً، ويتحقق منه من البداية إلى النهاية مقابل عميل حقيقي، ويوضح بالضبط أين يختلف الـ SDK المتاح.

## ملخص

تحتوي إضافة MCP Tasks المقرة على ثلاث طرق — `tasks/get`، و `tasks/update`، و `tasks/cancel` — ولا توجد `tasks/list`، وذلك عن قصد لأسباب أمنية[^1]. لا يزال موديول `experimental/tasks` في إصدار `@modelcontextprotocol/sdk` v1.30.0 يطبق شكلاً سابقاً يحتوي على `tasks/list` و `tasks/result`، ولا يحتوي على `tasks/update` على الإطلاق[^3]؛ أما حزم `@modelcontextprotocol/{core,server,client}` v2.0.0 الجديدة فلا تطبق أي طرق Tasks في مفردات النقل النشطة الخاصة بها على الإطلاق، وتحتفظ بذلك الشكل السابق فقط كصادرات لأنواع ميتة غير تشغيلية[^4]. الحزمة الوحيدة المنشورة رسمياً والتي تطابق المواصفات المقرة، وهي `@modelcontextprotocol/ext-tasks`، مخصصة لجانب الطالب (requester-side) فقط[^5]. يقوم هذا المنشور ببناء خادم صحيح وفقاً للمواصفات يدوياً باستخدام كلاس `Server` منخفض المستوى في الـ SDK، ويقوم بتشغيله مقابل عميل حقيقي، ويستعرض خطأً حقيقياً اكتشفته هذه العملية: إرجاع مقبض `setTimeout` شارد في سجل المهمة يؤدي إلى تعليق العميل بصمت، لأن وسيلة النقل (transport) لا يمكنها تحويله إلى سلسلة نصية (serialize) ولا تبلغ أبداً عن السبب.

## ما ستتعلمه

- الأشكال الدقيقة لنقل البيانات في `tasks/get` و `tasks/update` و `tasks/cancel` وفقاً لمواصفات 2026-07-28 المقرة، بما في ذلك دورة `input_required`
- لماذا يثبت كود التحكم في القدرات (capability-gating) الخاص بـ `@modelcontextprotocol/sdk` — وليس فقط وثائقه — أنه لا يعرف شيئاً عن `tasks/update`
- كيف تقارن حزم SDK v2 المقسمة (`@modelcontextprotocol/core` و `/server` و `/client`)، وأين تقع `@modelcontextprotocol/ext-tasks` كالحزمة الوحيدة المتوافقة مع المواصفات المقرة والموجودة حالياً
- كيفية تنفيذ طرق المهام الثلاث يدوياً باستخدام كلاس `Server` منخفض المستوى، بما في ذلك إعلان قدرة `tasks` التي يفرضها الـ SDK في وقت التشغيل
- خطأ حقيقي لم يتم اكتشافه إلا بتشغيل الكود: قيمة غير قابلة للتحويل إلى سلسلة نصية (non-serializable) في سجل المهمة تؤدي إلى تعليق العميل دون ظهور خطأ في أي من الجانبين
- كيف تبدو دورة حياة المهمة الكاملة من البداية إلى النهاية، من حالة `working` مروراً بـ `input_required` وصولاً إلى `completed`، مع مخرجات حقيقية مسجلة

## ما تحدده إضافة Tasks فعلياً

تعتبر المهام (Tasks) امتداداً لبروتوكول Model Context Protocol، وليست تغييراً في البروتوكول الأساسي — يتم التفاوض على الدعم لكل طلب على حدة، وقد يعيد الخادم مقبض مهمة (task handle) بدلاً من النتيجة العادية لأي نوع طلب يغطيه الامتداد (حالياً فقط `tools/call`)[^1]. عندما يقرر الخادم تأجيل استدعاء ما، فإنه يعيد `CreateTaskResult` — وهي نتيجة JSON-RPC تبدو عادية مع `resultType: "task"` وكائن `Task` مدمج بداخلها:

```json
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "resultType": "task",
    "taskId": "786512e2-9e0d-44bd-8f29-789f320fe840",
    "status": "working",
    "createdAt": "2025-11-25T10:30:00Z",
    "lastUpdatedAt": "2025-11-25T10:40:00Z",
    "ttlMs": 60000,
    "pollIntervalMs": 5000
  }
}
```

من هنا، يمتلك العميل ثلاث عمليات، ثلاث فقط[^1]:

| الطريقة (Method) | الغرض                                                                                                         | قاعدة ملحوظة                                                                                                          |
| ---------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| `tasks/get`      | التحقق من الحالة الحالية؛ الحالات النهائية تدرج الـ `result` أو الـ `error` الخاصة بها مباشرة في الاستجابة    | لا يوجد استدعاء منفصل لـ "جلب النتيجة" — استدعاء `tasks/get` لمهمة بحالة `completed` يعيد النتيجة نفسها               |
| `tasks/update`   | الإجابة على حالة `input_required` للمهمة عبر `inputResponses`، المرتبطة بـ `inputRequests` التي أرسلها الخادم | التأكيد يكون بنظام "أرسل وانسَ" (fire-and-forget)؛ يستأنف العميل التحقق عبر `tasks/get` لرؤية التأثير                 |
| `tasks/cancel`   | إرسال إشارة بالرغبة في إيقاف المهمة                                                                           | تعاوني فقط — قد ينهي الخادم المهمة على أي حال، والعميل غير ملزم بانتظار حالة `cancelled` قبل التخلص من الحالة المحلية |

تنتقل الـ `Task` عبر خمس حالات ممكنة: `working`، `input_required`، `completed`، `cancelled`، `failed`[^1]. المواصفات صريحة في أنه لا يوجد تعمد لوجود `tasks/list`:

> "بسبب عدم وجود `tasks/list`، لا يمكن للخادم أن يسرب عن غير قصد وجود مهام خاصة بمستدعي إلى مستدعي آخر. هذا تحسين عن مواصفات المهام بتاريخ `2025-11-25`، حيث كان من الممكن لقائمة ذات نطاق ضعيف أن تكشف عن معرفات مهام غير مرتبطة."[^1]

هذا السطر مهم لما سيلي، لأن كود الـ SDK الذي يتم شحنه حالياً لا يزال يحتوي على طريقة `tasks/list`.

## ما الذي يتم شحنه فعلياً: ثلاثة تنفيذات، مواصفة واحدة

قبل كتابة سطر واحد من كود الخادم، من المفيد معرفة ما يمكن تثبيته فعلياً اليوم (تم التحقق في 2026-09-18). هناك ثلاثة أماكن منفصلة يتواجد فيها منطق المهام (Tasks) في منظومة TypeScript، وهي لا تتفق مع بعضها البعض.

**`@modelcontextprotocol/sdk@1.30.0`** — خط الـ SDK v1.x المستمر — يشحن وحدة `./experimental/tasks`. تثبيتها وقراءة ملف `dist/esm/types.js` المجمع مباشرة يظهر سلاسل طرق JSON-RPC الحرفية التي يسجلها: `tasks/get`، `tasks/result`، `tasks/list`، `tasks/cancel`، وطريقة إشعارات `notifications/tasks/status`[^3]. لا يوجد `tasks/update` في أي مكان في الحزمة. هذا هو شكل مسودة `2025-11-25` الذي يصفه قسم الأمان في المواصفات بأنه تم استبداله، وليس النسخة المعتمدة.

**`@modelcontextprotocol/{core,server,client}@2.0.0`** — خط الإصدار v2 الذي تم تقسيمه حديثاً — يروي قصة أكثر وضوحاً مما يوحي به البحث السطحي عن سلاسل الطرق (method strings). المخرجات المجمعة (compiled output) منظمة في سجلات "عصر السلك" (wire era) منفصلة ومؤرخة: منطقة قديمة `wire/rev2025-11-25/registry.ts`، ومنطقة نشطة `wire/rev2026-07-28/registry.ts` وهي التي يرجع إليها إرسال الطلبات المباشر في الـ SDK فعلياً. لا تزال `tasks/get` و `tasks/list` و `tasks/result` و `tasks/cancel` و `notifications/tasks/status` موجودة في المخرجات المجمعة[^4] — ولكن فقط داخل السجل القديم، ويقرأ الـ JSDoc الموجود فوق كل منها مباشرة "`@deprecated 2025-11-25 wire vocabulary with no SDK runtime; kept importable for interoperability only`." أما السجل *النشط* بتاريخ 2026-07-28 — وهو السجل الذي تدعم فيه `requestMethodKeys`/`notificationMethodKeys` عملية إرسال الـ SDK الذي يعمل حالياً — فلا يحتوي على أي مدخلات لـ `tasks/*` أو `notifications/tasks*`، سواء بالشكل القديم أو الجديد[^4]. لذا، لم يفشل v2 فقط في اللحاق بالشكل المعتمد المكون من ثلاث طرق؛ بل أسقطت مفردات السلك المباشرة لديه الـ Tasks تماماً، ولم يتبقَ الشكل القديم إلا كأنواع قديمة خاملة لا يراها أي معالج طلبات (request handler). وتؤكد مشكلة GitHub تم تقديمها بناءً على طلب أحد مطوري MCP بينما كان v2 لا يزال في المرحلة التجريبية (beta) أن هذا لم يكن حادثاً: "قام v2 بإزالة المهام التجريبية وفقاً لـ SEP-2663،" بانتظار إعادة تنفيذ صحيحة بمجرد أن تلحق حزمة الإضافة نفسها بالركب[^6]. وحتى وقت كتابة هذه السطور، أصبح v2.0.0 متاحاً للعموم (GA) ولا السجل النشط ولا السجل القديم يطبقان الشكل المعتمد.

**`@modelcontextprotocol/ext-tasks@0.1.0`** هي الحزمة الوحيدة المنشورة رسمياً من منظمة `modelcontextprotocol` والتي تتطابق مع ذلك. إنها التنفيذ المرجعي الرسمي للإضافة المعتمدة، والمنشورة من مستودع GitHub الخاص بـ `modelcontextprotocol/ext-tasks`[^5]. يحتوي ملف المخطط (schema) المجمع لوحدة `core/v2` الخاصة بها على مجموعة الطرق المعتمدة بالضبط — `tasks/get` و `tasks/update` و `tasks/cancel` و `notifications/tasks` — وهو ما تم تأكيده من خلال البحث في الـ JavaScript المبني، وليس مجرد قراءة سورس TypeScript[^5]. لكن الحزمة مخصصة لجانب الطالب (requester-side) فقط: فنقطة دخول الـ `client` الخاصة بها (`createTaskSessionFromClient` و `withTasks`) تدير استدعاء أداة معزز بالمهام من العميل، وتتطلب صراحةً مثيلاً من `Client` الخاص بـ `@modelcontextprotocol/client` v2، وليس الخاص بـ SDK v1.x[^5]. لا يوجد مساعد مكافئ منشور لجانب الخادم (server-side). أما وحدة الـ `receiver` المنفصلة فترتبط بنفس نوع `Client` v2 مثل جانب الطالب، ولكن موثق أنها تتعامل مع طلبات Tasks بتاريخ `2025-11-25` — وهو الشكل القديم مرة أخرى، ولسيناريو مختلف (أخذ عينات واستنتاج من الخادم إلى العميل، وليس `tools/call`).

بالجمع بين هذه النقاط: إذا كنت تريد خادماً يتحدث بلغة إضافة Tasks المعتمدة المكونة من ثلاث طرق اليوم، فلا توجد حزمة منشورة توفر لك ذلك. يجب عليك تنفيذها مقابل بدائيات البروتوكول منخفضة المستوى، وهو ما سيفعله بقية هذا المنشور.

## بناء الخادم يدوياً

يستخدم خادم العرض أدناه كلاس `Server` منخفض المستوى الخاص بـ `@modelcontextprotocol/sdk@1.30.0` — حيث تشير تعريفات الأنواع الخاصة بـ SDK إلى أنه `@deprecated` مع ملاحظة "استخدم `McpServer` بدلاً من ذلك من أجل API عالي المستوى. استخدم `Server` فقط لحالات الاستخدام المتقدمة"، وهو ما ينطبق على تنفيذ إضافة غير مدعومة بعد[^3]. تم كتابة مخططات السلك (wire schemas) من الصفر بناءً على نص المواصفات المقتبس أعلاه، ولم يتم استيرادها من `experimental/tasks`، لأن تلك الوحدة تستخدم طرقاً خاطئة.

```ts
// schemas.ts — Zod schemas transcribed directly from the ratified spec.
import { z } from "zod";

export const TaskStatusSchema = z.enum([
  "working", "input_required", "completed", "cancelled", "failed",
]);

export const TaskSchema = z.object({
  taskId: z.string(),
  status: TaskStatusSchema,
  statusMessage: z.string().optional(),
  createdAt: z.string(),
  lastUpdatedAt: z.string(),
  ttlMs: z.number().nullable(),
  pollIntervalMs: z.number().optional(),
});

export const CreateTaskResultSchema = TaskSchema.extend({
  resultType: z.literal("task"),
});

export const GetTaskRequestSchema = z.object({
  method: z.literal("tasks/get"),
  params: z.object({ taskId: z.string() }),
});

export const UpdateTaskRequestSchema = z.object({
  method: z.literal("tasks/update"),
  params: z.object({
    taskId: z.string(),
    inputResponses: z.record(z.string(), z.object({
      action: z.string(),
      content: z.record(z.string(), z.unknown()).optional(),
    })),
  }),
});

export const CancelTaskRequestSchema = z.object({
  method: z.literal("tasks/cancel"),
  params: z.object({ taskId: z.string() }),
});
```

(يحدد الملف الكامل أيضاً مخططات مطابقة وهي `GetTaskResultSchema` و `UpdateTaskResultSchema` و `CancelTaskResultSchema` لاستجابات الطرق الثلاث، متبعة نفس النمط — تم اختصارها هنا لتوفير المساحة.)

تظهر أول مفاجأة في وقت التشغيل فوراً عندما تحاول تسجيل معالج (handler) لـ `tasks/get`. يقوم تابع `setRequestHandler` الخاص بـ `Server` منخفض المستوى باستدعاء فحص داخلي `assertRequestHandlerCapability(method)` قبل قبول التسجيل، ومصدره المترجم يحدد بدقة الطرق التي يتعرف عليها هذا الفحص:

```js
// from @modelcontextprotocol/sdk@1.30.0's compiled server/index.js
case 'tasks/get':
case 'tasks/list':
case 'tasks/result':
case 'tasks/cancel':
    if (!this._capabilities.tasks) {
        throw new Error(`Server does not support tasks capability (required for ${method})`);
    }
    break;
```

ينتج عن جملة switch هذه أمران. أولاً، يجب عليك التصريح عن `capabilities: { tools: {}, tasks: {} }` عند إنشاء `Server`، وإلا فإن تسجيل معالج `tasks/get` أو `tasks/cancel` سيؤدي إلى خطأ فوراً. ثانياً، `tasks/update` ليست موجودة في تلك القائمة على الإطلاق — فبوابة القدرات (capability gate) في SDK ليس لديها أي مفهوم عنها، لذا فإن تسجيل معالج لها لا يتطلب ولا يستفيد من علامة قدرة `tasks`. هذه الفجوة في حد ذاتها هي دليل عملي على أن بنية قدرات المهام في SDK المتاحة تم بناؤها قبل شكل `tasks/update`.

```ts
// server.ts — advertise tools + tasks, register the standard tool methods,
// then register the three Tasks methods by hand.
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import { GetTaskRequestSchema, UpdateTaskRequestSchema, CancelTaskRequestSchema } from "./schemas.js";

const server = new Server(
  { name: "tasks-demo-server", version: "0.1.0" },
  { capabilities: { tools: {}, tasks: {} } },
);
```

الأداة نفسها، `summarize_report`، تحول دائماً إلى مهمة (task) عندما يصرح المستدعي بدعم الإضافة، وتعود إلى نتيجة متزامنة عادية خلاف ذلك — حيث تنص المواصفات صراحة على أن "الخادم **يجب ألا** يعيد `CreateTaskResult` إلى عميل لم يدرج قدرة الإضافة في طلبه"[^1]:

```ts
function clientDeclaredTasksCapability(meta: unknown): boolean {
  if (!meta || typeof meta !== "object") return false;
  const caps = (meta as Record<string, unknown>)["io.modelcontextprotocol/clientCapabilities"];
  const extensions = caps && typeof caps === "object" ? (caps as any).extensions : undefined;
  return !!extensions && "io.modelcontextprotocol/tasks" in extensions;
}

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const wantsTasks = clientDeclaredTasksCapability((request.params as any)._meta);
  if (!wantsTasks) {
    return { content: [{ type: "text", text: "Summary: (synchronous fallback)" }] };
  }

  const taskId = crypto.randomUUID();
  const created = new Date().toISOString();
  const record = {
    taskId, status: "working" as const,
    createdAt: created, lastUpdatedAt: created,
    ttlMs: 5 * 60 * 1000, pollIntervalMs: 500,
  };
  tasks.set(taskId, record);
  scheduleWork(taskId, (request.params.arguments as any)?.simulateFail); // simulated background work; see below for a bug it exposed
  return { resultType: "task", ...record };
});
```

## العميل، وحلقة الاستطلاع (poll loop)

يصرح العميل عن الإضافة بنفس الطريقة، في `_meta` ضمن طلب `tools/call`، ثم يدير حلقة الاستطلاع الخاصة به ضد `tasks/get` — مع الالتزام بـ `pollIntervalMs` وتغيير السلوك بناءً على `status`:

```ts
const initial = await client.request(
  {
    method: "tools/call",
    params: {
      name: "summarize_report",
      arguments: { reportId },
      _meta: {
        "io.modelcontextprotocol/clientCapabilities": {
          extensions: { "io.modelcontextprotocol/tasks": {} },
        },
      },
    },
  },
  CallToolOrTaskResultSchema,
);

if (initial.resultType !== "task") { /* handle the synchronous path */ }

let { taskId, pollIntervalMs = 1000 } = initial;
while (true) {
  await sleep(pollIntervalMs);
  const status = await client.request(
    { method: "tasks/get", params: { taskId } },
    GetTaskResultSchema,
  );
  pollIntervalMs = status.pollIntervalMs ?? pollIntervalMs;

  if (status.status === "input_required") {
    await client.request(
      {
        method: "tasks/update",
        params: {
          taskId,
          inputResponses: {
            redaction_level: { action: "accept", content: { level: "partial" } },
          },
        },
      },
      UpdateTaskResultSchema,
    );
    continue;
  }
  if (status.status === "completed") { console.log(status.result); break; }
  if (status.status === "failed") { console.log(status.error); break; }
  if (status.status === "cancelled") break;
}
```

## دورة الـ input\_required

تتوقف معظم التغطية الحالية للمهام عند الحالة البسيطة — إنشاء مهمة، استطلاعها، والحصول على نتيجة. الجزء الأكثر فائدة في المواصفات، والجزء الذي يميز المهام فعلياً عن نمط "الاستطلاع حتى الانتهاء" العادي، هو `input_required`: حيث يمكن للخادم إيقاف مهمة مؤقتاً في منتصف التنفيذ، وإظهار طلب واحد أو أكثر معلقين في `inputRequests`، وانتظار العميل للإجابة عليها من خلال `tasks/update` قبل الاستئناف[^1]. يطبق خادم العرض هذا الأمر عمداً: يتوقف العمل المحاكى لـ `summarize_report` بعد حوالي 1.5 ثانية للسؤال عن مستوى تنقيح معلومات الهوية الشخصية (PII) المراد تطبيقه، باستخدام نفس شكل `elicitation/create` الذي قد يستخدمه طلب استيضاح مباشر:

```json
{
  "status": "input_required",
  "inputRequests": {
    "redaction_level": {
      "method": "elicitation/create",
      "params": {
        "mode": "form",
        "message": "Report Q3-vendor-audit: how should PII be redacted in the summary?",
        "requestedSchema": {
          "type": "object",
          "properties": { "level": { "type": "string", "enum": ["none", "partial", "full"] } },
          "required": ["level"]
        }
      }
    }
  }
}
```

يجيب العميل بمفتاح مطابق في `inputResponses` عند استدعاء `tasks/update`. يكون الإقرار (acknowledgement) فارغاً عن قصد ومتسقاً في النهاية — حيث تسمح المواصفات للخادم بقبول الاستجابة والإقرار بها قبل أن تعكس حالة الاستطلاع للمهمة ذلك — لذا فإن التحرك الصحيح الوحيد للعميل هو الاستمرار في استطلاع `tasks/get`، وليس معاملة استجابة `tasks/update` نفسها كتأكيد على حدوث أي تغيير[^1].

## المخرجات الفعلية

تشغيل العميل مقابل الخادم من البداية للنهاية، بدون أي محاكاة (mocking)، ينتج عن ذلك هذا (مستوى التعتيم `partial`، ثم استدعاء ثانٍ يختبر عمدًا مسار `failed`):

```text
--- calling summarize_report(reportId=Q3-vendor-audit, simulateFail=false) ---
Task created: e08594eb-1dd4-4066-b120-d35b442513db (status=working, pollIntervalMs=500)
  poll 1: status=working
  poll 2: status=working
  poll 3: status=input_required
  server needs input: {
  mode: 'form',
  message: 'Report Q3-vendor-audit: how should PII be redacted in the summary?',
  requestedSchema: { type: 'object', properties: { level: [Object] }, required: [ 'level' ] }
}
  sent tasks/update with redaction level = partial
  poll 4: status=working
  poll 5: status=completed
  completed. result: {
  "content": [
    { "type": "text", "text": "Summary ready (redaction=partial): report is 3 pages, 2 action items, 1 blocked dependency." }
  ],
  "isError": false
}

--- calling summarize_report(reportId=Q3-vendor-audit-2, simulateFail=true) ---
Task created: 41652027-6ec0-43cb-b48b-074319c2bb1c (status=working, pollIntervalMs=500)
  poll 1: status=working
  poll 2: status=working
  poll 3: status=failed
  failed: { code: -32603, message: 'Upstream summarizer timed out' }
```

كما أكد تشغيل منفصل حالتي الحافة اللتين ذكرتهما المواصفات صراحةً: العميل الذي يتجاهل إعلان القدرات (capability declaration) يحصل على نتيجة التراجع المتزامنة العادية، ولا يحصل أبدًا على `CreateTaskResult`[^1]، وإرسال `tasks/cancel` ضد مهمة `working` ينقلها إلى حالة `cancelled` في عملية الاستطلاع (poll) التالية، وهو ما يتوافق مع كون الإلغاء تعاونيًا وليس مضمونًا[^1].

## الخطأ الذي سيتسبب في تجميد العميل بصمت

كان الإصدار الأول من معالج الأدوات في الخادم يخزن مقبض `setTimeout` الخاص بكل مهمة مباشرة على نفس الكائن الذي يعيده إلى العميل، وذلك لتسهيل عملية التنظيف عند الإلغاء:

```ts
const record = { taskId, status: "working", /* ...*/ };
tasks.set(taskId, record);
record._timer = setTimeout(() => { /* advance the task */ }, 1500);
return { resultType: "task", ...record }; // _timer included by the spread
```

تشغيل العميل مقابل هذا الإصدار لم يتسبب في ظهور خطأ في أي من الطرفين — بل تجمد ببساطة إلى الأبد عند أول استدعاء لـ `tools/call`. عمل معالج الخادم وعاد بنجاح؛ لكن العميل لم يتلقَّ استجابة أبدًا. السبب: كائن `NodeJS.Timeout` يحتوي على مراجع دائرية داخلية، لذا فإن تسلسله (serializing) كجزء من نتيجة JSON-RPC يفشل داخل مسار الكتابة في وسيلة النقل (transport)، وهذا الفشل لا يظهر مرة أخرى للطلب الذي تسبب فيه. الحل هو تفكيك الحقل غير القابل للتسلسل قبل الإعادة:

```ts
const { _timer, ...publicRecord } = record;
return { resultType: "task", ...publicRecord };
```

يتم الكشف عن هذا هنا لأنه بالضبط نوع نمط الفشل الذي يكون غير مرئي عند قراءة التوثيق ولا يظهر إلا من خلال تشغيل كود مدعوم بالمهام فعليًا مقابل عميل حقيقي — وهو ما يبدو أن جميع المقالات الحالية التي تقارن بين SDK والمواصفات لم تفعله.

## ثلاثة تنفيذات، جنبًا إلى جنب

|                                                    | `sdk` v1.30.0 `experimental/tasks`                            | `core`/`server`/`client` v2.0.0 (مدمج)                                          | `ext-tasks` v0.1.0 `core/v2`                    |
| -------------------------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------- | ----------------------------------------------- |
| يطابق مواصفات 2026-07-28 المعتمدة                  | لا — شكل `2025-11-25`                                         | لا — سجل السلك النشط لا يحتوي على طرق Tasks على الإطلاق[^4]                     | نعم[^5]                                         |
| الطرق المتوفرة                                     | `tasks/get`, `tasks/list`, `tasks/result`, `tasks/cancel`[^3] | لا يوجد نشط — نفس الطرق الأربعة موجودة فقط كأنواع قديمة مهجورة وغير تشغيلية[^4] | `tasks/get`, `tasks/update`, `tasks/cancel`[^5] |
| دعم `tasks/update` / `input_required`              | لا                                                            | لا                                                                              | نعم، من جانب العميل                             |
| نشر مساعد من جانب الخادم                           | نعم (شكل خاطئ)                                                | لا — توجد أنواع مساعدة قديمة ولكنها لا تحمل وقت تشغيل SDK[^4]                   | لا — من جانب الطالب فقط                         |
| قابل للاستخدام اليوم لخادم يتبع المواصفات المعتمدة | لا                                                            | لا                                                                              | لا (العميل فقط)                                 |

## التحقق

تم التأكد من كل رقم إصدار، واسم ميثود (method)، وسلوك تقييد القدرات (capability-gating) في هذا المنشور من خلال تثبيت الحزم الفعلية وقراءة مخرجاتها المجمعة (compiled output)، وليس من خلال قراءة التوثيق وحده. تم تحديد `@modelcontextprotocol/sdk@1.30.0` على أنه علامة `latest` الحالية لـ `npm` وقت الكتابة؛ وتم عمل grep مباشرة لملفات `dist/esm/types.js` و `dist/esm/server/index.js` للبحث عن نصوص ميثودز JSON-RPC وجملة switch الخاصة بتقييد القدرات المقتبسة أعلاه. كما تم تثبيت `@modelcontextprotocol/core@2.0.0` و `/server@2.0.0` و `/client@2.0.0` بنفس الطريقة وعمل grep لنفس النصوص، مما أدى إلى قراءة الكود المجمع المحيط بها مباشرة — وهذا ما كشف عن وجود سجلين منفصلين من عصر الـ wire ووجود JSDoc الخاص بـ `@deprecated ... no SDK runtime` على السجل القديم. كما تم تثبيت `@modelcontextprotocol/ext-tasks@0.1.0` وقراءة ملفات `core/v2` و `client` و `receiver` من نوع `.d.ts` والملفات المجمعة `.js` مباشرة للتأكد من الميثودز ونوع العميل الذي تستهدفه كل نقطة دخول. كما تمت قراءة قضية GitHub المتعلقة بإزالة المهام في نسخة beta v2 بالكامل، ولم يتم تلخيصها من مصدر ثانوي.

كود السيرفر والعميل الموضح أعلاه تمت كتابته في مشروع حقيقي (Node.js v22.23.2, TypeScript 7.0.2, tsx 4.23.13, Zod 4.6.5)، وتم فحص الأنواع باستخدام `tsc --strict --noEmit`، وتم تنفيذه من البداية للنهاية عبر ناقل stdio حقيقي بين عمليتين منفصلتين — وليس بشكل وهمي (mocked). كل انتقال حالة موضح في قسم "المخرجات الحقيقية"، بما في ذلك دورة `input_required`/`tasks/update`، والحالات النهائية `completed` و `failed`، ومسار التراجع المتزامن (synchronous-fallback)، والإلغاء، تم اختبارها عن طريق تشغيل الكود والتقاط مخرجات الكونسول الفعلية، والتي تمت إعادة إنتاجها أعلاه مع تعديل المسافات البيضاء فقط لتناسب عرض السطر. تم اكتشاف خطأ تسلسل (serialization bug) الـ `_timer` بهذه الطريقة، ولم يتم اختلاقه من أجل المنشور.

## الأسئلة الشائعة

**ما هي الميثودز الثلاث في إضافة MCP Tasks؟** `tasks/get` (لفحص الحالة، مع إدراج النتيجة أو الخطأ بمجرد الوصول للحالة النهائية)، و `tasks/update` (للرد على طلب `input_required`)، و `tasks/cancel` (لطلب الإلغاء). لا توجد ميثود `tasks/list` عن قصد[^1].

**هل يدعم MCP TypeScript SDK الرسمي إضافة Tasks المعتمدة؟** ليس حتى وقت كتابة هذا المنشور. لا يزال موديول `experimental/tasks` في `@modelcontextprotocol/sdk@1.30.0` يطبق شكل `2025-11-25` ما قبل الاعتماد. أما حزم `@modelcontextprotocol/core`/`server`/`client@2.0.0` المنفصلة حديثاً فقد ذهبت أبعد في الاتجاه الآخر: مفردات الـ wire النشطة لديهم لا تطبق أي ميثودز لـ Tasks على الإطلاق، وتحتفظ بشكل `2025-11-25` فقط كصادرات أنواع ميتة (dead type exports) محددة صراحة بـ "no SDK runtime". تم التأكد من ذلك من خلال فحص نصوص ميثودز JSON-RPC المجمعة وكود السجل مباشرة[^3][^4].

**هل هناك أي حزمة رسمية تطبق المواصفات المعتمدة؟** موديولات `core/v2` و `client` في `@modelcontextprotocol/ext-tasks@0.1.0` تتطابق مع شكل الميثودز الثلاث المعتمدة، ولكن الحزمة مخصصة لجانب الطالب (العميل) فقط — لا يوجد مساعد منشور لجانب السيرفر لها حتى الآن[^5].

**ماذا يحدث إذا قام عميل باستدعاء أداة مدعومة بالمهام (task-augmented tool) دون التصريح عن قدرة الإضافة؟** وفقاً للمواصفات، يجب ألا يعيد السيرفر `CreateTaskResult` في هذه الحالة. في سيرفر العرض الخاص بهذا المنشور، تعيد الأداة بدلاً من ذلك نتيجة متزامنة عادية[^1].

**هل `tasks/cancel` يضمن إيقاف المهمة؟** لا. الإلغاء تعاوني: الخادم يقر باستلام الطلب ولكنه يقرر ما إذا كان سيوقف العمل فعلياً ومتى، وقد تصل المهمة إلى حالة نهائية أخرى غير `cancelled`[^1].

## الخلاصة

تعتمد إضافة MCP Tasks المعتمدة تصميماً نظيفاً يتكون من ثلاث طرق — `tasks/get`، و `tasks/update`، و `tasks/cancel` — مع تدفق مدخلات مفيد وحقيقي أثناء تنفيذ المهمة. ما ليس صحيحاً بعد، حتى سبتمبر 2026، هو أن تثبيت SDK الرسمي يمنحك تنفيذاً لها: فإصدار v1.x المستقر لا يزال يستخدم شكل الإضافة السابق قبل الاعتماد، وحزم v2 الجديدة لا تدعم أي إصدار منها في مفردات الربط النشطة على الإطلاق، والحزمة الوحيدة المنشورة رسمياً والتي تتوافق مع المواصفات المعتمدة تغطي جانب العميل فقط. بناء خادم متوافق اليوم يعني تجاوز مساعدات `experimental/tasks` في SDK وتنفيذ الطرق الثلاث مباشرة وفقاً لتنسيق الربط الموثق — وهو أمر يتطلب بضع عشرات من أسطر الكود، وليس إطار عمل كاملاً، بمجرد أن تدرك أن جملة switch الخاصة ببوابة القدرات لا تتعرف إلا على أسماء الطرق القديمة.

## قراءات ذات صلة

- [خادم MCP في TypeScript: OAuth 2.1 + Streamable HTTP (2026)](https://nerdleveltech.com/mcp-server-typescript-oauth-streamable-http-production-tutorial)
- [MCP يصبح Stateless: شرح مواصفات 2026-07-28](https://nerdleveltech.com/mcp-stateless-protocol-enterprise-authorization)
- [شرح خوادم MCP: العمود الفقري الجديد للذكاء الاصطناعي في Claude](https://nerdleveltech.com/mcp-servers-explained-claudes-new-ai-backbone-for-real-automation)

[^1]: Model Context Protocol, "Tasks" (MCP Tasks Extension, spec version 2026-07-28) — <https://tasks.extensions.modelcontextprotocol.io/specification/draft/tasks> (three methods, `Task` status enum, capability negotiation, `input_required`/`inputRequests`/`inputResponses` flow, security considerations including the removal of `tasks/list`, cancellation semantics; fetched 2026-09-18)

[^2]: Model Context Protocol, "Specification" (2026-07-28) — <https://modelcontextprotocol.io/specification/2026-07-28> (lists Tasks as an official extension of the 2026-07-28 spec release; fetched 2026-09-18)

[^3]: `@modelcontextprotocol/sdk` v1.30.0 on npm — <https://registry.npmjs.org/@modelcontextprotocol/sdk/latest> (resolved as `latest`; `experimental/tasks` export path, method literals `tasks/get`/`tasks/result`/`tasks/list`/`tasks/cancel`/`notifications/tasks/status` confirmed in compiled `dist/esm/types.js`; capability-gating switch statement confirmed in `dist/esm/server/index.js`; `Server` class documented `@deprecated`; installed and inspected directly, 2026-09-18)

[^4]: `@modelcontextprotocol/core`, `@modelcontextprotocol/server`, `@modelcontextprotocol/client`, all v2.0.0 on npm (installed and inspected directly, 2026-09-18) — compiled `dist` output contains two versioned wire-era registries; the `wire/rev2025-11-25/registry.ts` region defines `tasks/get`, `tasks/cancel`, `tasks/list`, `tasks/result`, and `notifications/tasks/status`, each documented `@deprecated 2025-11-25 wire vocabulary with no SDK runtime; kept importable for interoperability only`; the active `wire/rev2026-07-28/registry.ts` region's `requestMethodKeys`/`notificationMethodKeys` — which the SDK's live dispatch consults — contain no `tasks/*` or `notifications/tasks*` entries at all; `tasks/update` is absent from both registries

[^5]: `@modelcontextprotocol/ext-tasks` v0.1.0 on npm, reference implementation from <https://github.com/modelcontextprotocol/ext-tasks> (based on SEP-2663; Apache-2.0; installed and inspected directly, 2026-09-18) — `core/v2/schemas.js` method literals `tasks/get`/`tasks/update`/`tasks/cancel`/`notifications/tasks` confirmed matching the ratified spec; `client` entry point requires `@modelcontextprotocol/client` (v2); `receiver` entry point documented as handling `2025-11-25`-era sampling/elicitation task requests

[^6]: GitHub, `modelcontextprotocol/modelcontextprotocol` issue #3051, "docs: update or freeze SEP-1686 tasks code examples for the v2 SDK" — <https://github.com/modelcontextprotocol/modelcontextprotocol/issues/3051> (filed 2026-07-08 at maintainer David Soria Parra's request; states TypeScript SDK v2 was then at `2.0.0-beta.2` as split `@modelcontextprotocol/{client,server,core,node,express,hono,server-legacy}` packages and that "v2 removed experimental tasks per SEP-2663"; fetched 2026-09-18)
