بناء خادم MCP بعيد في Next.js باستخدام mcp-handler 2: الأدوات وOAuth ومواصفة 2026-07-28 عديمة الحالة
جدول المحتويات
عن الكاتب

Ahmed Mahmoud
كاتب ومطوّر
مهندس برمجيات شغوف بتطوير الويب وتصميم تجربة المستخدم.
مؤسّس ديفيا · eng-ahmed.com ↗أهم النقاط
بروتوكول Model Context Protocol (MCP) هو بروتوكول مفتوح يتيح لعملاء الذكاء الاصطناعي اكتشاف الأدوات الخارجية واستدعاءها. خادم MCP البعيد يعرض هذه الأدوات عبر Streamable HTTP بدلاً من عملية محلية تعمل عبر stdio.
الإصدار 2.x من mcp-handler يغلّف الإصدار الثاني من MCP TypeScript SDK الرسمي المنشور باسم @modelcontextprotocol/server، ويعيد معالج طلبات قياسياً من نوع Web يصدّره Route Handler في Next.js باسم GET وPOST.
مواصفة MCP بإصدار 2026-07-28 عديمة الحالة (stateless): لا جلسات، وغلاف _meta مع كل طلب، ودالة server/discover. ويظل mcp-handler 2 يخدم عملاء Streamable HTTP من حقبة 2025 من المسار نفسه، مع إلغاء نقل HTTP+SSE والاستغناء عن Redis.
تصميم الأداة هو ما يحدد هل سيستدعيها النموذج بشكل صحيح: مهمة واحدة لكل أداة، ووصف مكتوب كأنه تعليمات، ومخططات إدخال صارمة، وoutputSchema مع structuredContent، ونتائج isError يستطيع النموذج التعافي منها.
الدالة withMcpAuth تتحقق من رموز bearer وترد بتحديات WWW-Authenticate وفق RFC 9728. ومواصفة 2026-07-28 تُوقف العمل بالتسجيل الديناميكي للعملاء (DCR) لصالح مستندات بيانات تعريف العميل (CIMD) التي يطبّقها خادم التفويض.
ما هو خادم MCP البعيد، ومتى يستحق البناء؟
خادم MCP البعيد هو نقطة HTTPS تتحدث بروتوكول Model Context Protocol، بحيث يستطيع أي عميل MCP عرض أدواته واستدعاءها نيابة عن المستخدم. خادم MCP المحلي يعمل كعملية فرعية عبر stdio على جهاز المستخدم، أما الخادم البعيد فيعمل بجوار واجهة برمجة المنتج، ويستخدم نظام المصادقة نفسه، ويخدم مستخدمين لن يثبّتوا أي أداة سطر أوامر.
في Devya نبني خادم MCP بعيداً عندما يملك المنتج واجهة برمجية يريد المستخدمون أن يقودها مساعد ذكي: البحث في السجلات، أو تلخيص حساب، أو إنشاء مسودة. ونتجاوزه عندما تحتاج المهمة إلى ملفات محلية أو سطر أوامر، فهذا ما زال مجال stdio. والقاعدة التي يلتزم بها فريقنا: خادم MCP هو عميل إضافي لطبقة المجال تماماً مثل واجهة الويب، ويمر عبر فحوص التفويض نفسها.
كيف نركّب خادم MCP داخل Route Handler في Next.js؟
الدالة createMcpHandler من mcp-handler تستقبل دالة تهيئة تسجّل الأدوات على الخادم، وتعيد معالج طلبات قياسياً من نوع Web. نصدّر هذا المعالج باسم GET وPOST من مسار مثل app/api/mcp/route.ts، والمسار /api/mcp مجرد عُرف وليس شرطاً، لأن mcp-handler 2 لا يفحص مسار الطلب أبداً.
تُسجَّل كل أداة عبر server.registerTool مع كائن إعدادات يضم: عنواناً، ووصفاً، وinputSchema مبنياً بـ z.object من Zod 4، وoutputSchema اختيارياً، وannotations. وتستقبل دالة الأداة المعاملات بعد التحقق منها إضافة إلى كائن ctx، ويحمل ctx.http?.authInfo بيانات المستخدم المُتحقق منه بعد إضافة OAuth.
ثلاث تفاصيل تؤدي معظم العمل. الحقل inputSchema مخطط Standard Schema كامل، لذلك يتحقق الـ SDK من المعاملات قبل تشغيل الدالة ويحوّل الرفض إلى نتيجة أداة يستطيع النموذج تصحيحها. والجمع بين outputSchema وstructuredContent يمنح العملاء حمولة JSON مُنمّطة بجوار نص content المقروء. وannotations مثل readOnlyHint: true تخبر المضيف بأن الأداة تقرأ البيانات فقط، وهو ما يعتمد عليه المضيف عند تقرير طلب موافقة المستخدم.
مسار mcp-handler 2 عديم الحالة، لذلك يعمل على Vercel Functions أو أي خادم Node.js دون جلسات لاصقة. كل طلب يحمل كل ما يحتاجه الخادم، ولهذا اختفت تبعية Redis الموجودة في الإصدار 1.x.
ما الذي تغيّر في mcp-handler 2 وفي مواصفة MCP بإصدار 2026-07-28؟
يستبدل mcp-handler 2 حزمة الـ SDK وشكل المخطط وقائمة طرق النقل، لذلك لا يُترجَم مسار مكتوب للإصدار 1.x دون تعديل. هذه هي التعديلات التي احتاجها ترحيلنا:
حزمة الـ SDK: تُستبدل @modelcontextprotocol/sdk v1 بـ @modelcontextprotocol/server v2 مع zod 4.
تسجيل الأدوات: أُزيلت الصيغة المتغيرة server.tool(...)، وبقيت registerTool وregisterPrompt وregisterResource.
الحقل inputSchema: يُستبدل شكل Zod الخام بمخطط Standard Schema كامل مثل z.object({ ... }).
بيانات المصادقة داخل الأداة: يصبح extra.authInfo هو ctx.http?.authInfo.
طرق النقل: أُزيل HTTP+SSE ومعه Redis، ولم يبقَ إلا Streamable HTTP عديم الحالة.
التوجيه والتوقيع: اختفت خيارات basePath وmaxDuration وsessionIdGenerator، ويُركَّب المعالج على أي مسار، وتصبح createMcpHandler(init, serverOptions, config) هي createMcpHandler(init, options).
على مستوى البروتوكول، تُلغي مواصفة MCP بإصدار 2026-07-28 الجلسات: كل طلب يحمل غلاف _meta، ويستطيع العملاء استدعاء server/discover لمعرفة ما يدعمه الخادم. والعملاء الذين ما زالوا يتحدثون Streamable HTTP من حقبة 2025 يستمرون في العمل على المسار نفسه عبر المسار الاحتياطي عديم الحالة في الـ SDK، بينما ترد عمليات الجلسة عبر GET وDELETE بالرمز 405.
كيف تُصمَّم أدوات MCP بحيث يستدعيها النموذج بشكل صحيح؟
يختار النموذج أدوات MCP ويملأ معاملاتها اعتماداً على أسمائها وأوصافها ومخططاتها فقط، لذلك نتعامل مع تصميم الأدوات على أنه تصميم تعليمات مدعوم بنظام أنواع.
مهمة مستخدم واحدة لكل أداة، وليس أداة لكل نقطة REST: أداتا search_invoices وcreate_invoice_draft أفضل من أداة عامة call_api تأخذ معامل method.
اكتب الوصف كأنه تعليمات: ماذا تُرجع الأداة، وما حدودها، وأي أداة تُستدعى بعدها. سطر مثل «تُرجع 20 نتيجة كحد أقصى، الأحدث أولاً» يوفّر استدعاءً ضائعاً.
قيّد المدخلات بقيم enum وحدود min وmax وتلميحات .describe(). كل قيد يفرضه المخطط هو قيد لا يستطيع النموذج أن يخطئ فيه بصمت.
أرجِع نصاً مختصراً مع structuredContent. تفريغ JSON ضخم يملأ نافذة سياق العميل، لذلك نلخّص في content ونضع الحمولة المُنمّطة في structuredContent.
أرجِع isError: true مع رسالة قابلة للتنفيذ عند الإخفاقات المتوقعة. يتعامل MCP TypeScript SDK مع الخطأ المُلقى ونتيجة isError الصريحة بالطريقة نفسها، ويتخطى التحقق من outputSchema في نتائج الخطأ.
صِف أدوات الكتابة بصدق عبر destructiveHint وidempotentHint. هذه التوصيفات مجرد تلميحات لنوافذ الموافقة لدى العميل ولا تفرض شيئاً، لذلك يبقى فحص التفويض داخل دالة الأداة.
مخرجات أدوات MCP هي أيضاً مدخلات غير موثوقة لدور النموذج التالي. السجل الذي يحتوي على تعليمات سيصل إلى سياق النموذج، لذلك نُبقي أدوات الكتابة ضيقة النطاق، ولا نسمح أبداً لمخرجات أداة بتحديد صلاحيات أداة أخرى.
كيف نضيف OAuth إلى خادم MCP بعيد؟
تغلّف withMcpAuth معالج MCP، وتستخرج رمز bearer، وتستدعي دالة تحقق، وترد بالرمز 401 أو 403 مع تحدي WWW-Authenticate وفق RFC 9728 يوجّه العميل إلى بيانات تعريف المورد المحمي. تُرجع دالة التحقق كائن AuthInfo يضم الرمز وclientId وscopes وحقلاً إضافياً مثل معرّف المستخدم، وتفرض خيارات مثل required: true وrequiredScopes صلاحيات الوصول قبل تشغيل أي أداة.
مستند Protected Resource Metadata وفق RFC 9728 هو مستند JSON يخبر عميل MCP بخادم التفويض الذي يُصدر الرموز للمورد. ويقدّمه mcp-handler من المسار app/.well-known/oauth-protected-resource/route.ts عبر protectedResourceHandler مع قائمة authServerUrls، إضافة إلى metadataCorsOptionsRequestHandler لطلب OPTIONS التمهيدي.
قاعدتان لا تفاوض فيهما لدى فريقنا. نتحقق من جمهور الرمز (audience) ولا نقبل إلا الرموز الصادرة لعنوان خادم MCP، ولا نمرّر رمز العميل أبداً إلى خدمات أخرى. ونُبقي الرمز خارج مخرجات الأدوات والسجلات.
تغيّر تسجيل العملاء في مواصفة 2026-07-28. أصبح التسجيل الديناميكي للعملاء (DCR) متوقفاً لصالح مستندات بيانات تعريف العميل (CIMD)، حيث يعرّف عميل OAuth نفسه بعنوان HTTPS يقدّم بياناته الوصفية. يطبّق خادم التفويض CIMD ويعلن عنه عبر client_id_metadata_document_supported في بياناته الوصفية وفق RFC 8414، لذلك يبقى مسار MCP كما هو.
كيف نختبر خادم MCP بعيداً ونوصله بعملاء حقيقيين؟
نختبر على ثلاث طبقات. اختبارات الوحدة تستدعي دوال المجال مباشرة، لأن دالة الأداة يجب أن تكون محوّلاً رقيقاً. وأداة MCP Inspector (npx @modelcontextprotocol/inspector) تتصل بالمسار المحلي لنستدعي كل أداة يدوياً ونقرأ النتائج الخام، بما فيها أخطاء التحقق. كما يدعم MCP TypeScript SDK عميل Client داخل الذاكرة متصلاً بالخادم لاختبارات آلية على مستوى البروتوكول.
ثم عميل حقيقي. العملاء الذين يدعمون Streamable HTTP يأخذون عنوان المسار مباشرة، أما العملاء الذين يدعمون stdio فقط فيمرّون عبر الجسر mcp-remote باستخدام npx -y mcp-remote والعنوان نفسه.
يكشف الاختبار مع عميل حقيقي ما لا يكشفه Inspector: وصف مبهم يجعل النموذج يختار الأداة الخطأ، أو مخرجات أطول من سياق العميل. نعالج ذلك في الوصف وشكل الاستجابة، لا بإضافة مزيد من الأدوات.
الأسئلة الشائعة
س: هل نحتاج إلى Redis أو جلسات لاصقة لتشغيل خادم MCP على Vercel؟
ج: لا. يخدم mcp-handler 2 بروتوكول MCP دون حالة، فكل طلب مستقل ويعمل على Vercel Functions دون Redis. كان Redis مطلوباً فقط لنقل HTTP+SSE الذي أُزيل في mcp-handler 2.x.
س: هل يمكن لمشروع أن يستمر في استخدام @modelcontextprotocol/sdk مع mcp-handler 2؟
ج: لا. يعتمد mcp-handler 2 على @modelcontextprotocol/server ^2.0.0، وهو الإصدار الثاني من MCP TypeScript SDK، مع Zod 4. والمشروع الذي يبقى على @modelcontextprotocol/sdk v1 عليه أن يبقى على mcp-handler 1.x.
س: ماذا يحدث للعملاء الذين لا يدعمون إلا مواصفة MCP لعام 2025؟
ج: يستمرون في العمل. يخدم mcp-handler 2 نقل Streamable HTTP من حقبة 2025 عبر المسار الاحتياطي عديم الحالة في الـ SDK على المسار نفسه، وفقط عمليات الجلسة عبر GET وDELETE تُرجع 405. أما نقل HTTP+SSE من مواصفة 2024-11-05 فلم يعد مدعوماً.
س: هل يكفي readOnlyHint لمنع النموذج من تعديل البيانات؟
ج: لا. توصيفات أدوات MCP مثل readOnlyHint وdestructiveHint تلميحات يستخدمها العملاء في نوافذ الموافقة، ولا يفرضها الـ SDK. التفويض مكانه داخل دالة الأداة باستخدام المستخدم المُتحقق منه في ctx.http?.authInfo.
س: هل يعمل mcp-handler مع Next.js فقط؟
ج: لا. تُرجع createMcpHandler معالج طلبات قياسياً من نوع Web، لذلك يُركَّب في Route Handlers في Next.js ومسارات SvelteKit وNuxt/Nitro عبر fromWebHandler وHono عبر c.req.raw. أما Express فيحتاج إلى محوّل Web Request أو وسيط MCP الرسمي.
قراءات إضافية
هندسة الذكاء الاصطناعي
لماذا لا يوقف موجّه نظام أفضل حقنَ الأوامر: ملاحظاتنا الميدانية حول الثلاثية القاتلة والأدوات محددة الصلاحيات والمخرجات غير الموثوقة
أثناء بناء مساعد لصندوق الدعم يملك صلاحية استدعاء الأدوات، أدخل فريقنا تذكرة اختبار تحتوي على تعليمات مخفية، فنفّذ النموذج بعضها. هذه ملاحظاتنا الميدانية حول سبب كون حقن الأوامر مشكلة معمارية لا مشكلة صياغة، وكيف تكشف الثلاثية القاتلة ميزات الذكاء الاصطناعي الخطرة، وكيف نقيّد الأدوات بصلاحيات المستخدم المسجّل، ولماذا تحتاج الآثار الجانبية إلى تأكيد بشري، وكيف يتحول عرض Markdown إلى قناة لتسريب البيانات.
هندسة الذكاء الاصطناعي
لماذا تكلّف ميزات النماذج اللغوية أكثر مما ينبغي: ملاحظاتنا الميدانية حول التخزين المؤقت للموجّهات ونقاط القطع والطابع الزمني الذي يُفسد كل إصابة
استمرت فاتورة الوحدات الرمزية لأحد وكلائنا في الارتفاع بعد تفعيل التخزين المؤقت للموجّهات، وكشف حقل الاستخدام السبب: كل طلب كان كتابة للذاكرة المؤقتة ولا طلب واحد كان قراءة منها. هذه ملاحظاتنا الميدانية حول الموضع الصحيح لنقطة القطع، والتغييرات التي تُبطل البادئة بصمت، والحساب الذي يحدد الاختيار بين مدة خمس دقائق وساعة كاملة.