المفاهيم · فصل ٢

المعمارية: مين بيتكلم مع مين

Host · Client · Server · JSON-RPC · stdio · Streamable HTTP
🎯 الهدف: تفرّق بين الـ host والكلاينت والسيرفر، وبين طبقة الداتا وطبقة النقل.

الفصل ده نطاق البروتوكول ومثال كامل. الـ SDKs بتخبي تفاصيل كتير، أهم جزء هتحتاجه هو بروتوكول طبقة الداتا: إزاي السيرفر يدي سياق للتطبيق. تفاصيل التنفيذ في دوكس الـ SDK بتاعة لغتك.

المشروع ده فيه إيه؟

  • المواصفة: متطلبات التنفيذ للكلاينت والسيرفر.
  • الـ SDKs: مكتبات بلغات مختلفة بتنفّذ البروتوكول.
  • أدوات التطوير: أشهرها MCP Inspector.
  • تنفيذات مرجعية: سيرفرات جاهزة تتعلم منها.

تلات مشاركين

المعمارية client-server. الـ host هو تطبيق الذكاء (زي Claude Code أو Claude Desktop). بيعمل كلاينت واحد لكل سيرفر، وكل كلاينت ماسك اتصال مخصص.

Host

تطبيق الذكاء. بينسّق ويدير كلاينت واحد أو أكتر.

Client

مكون ماسك اتصال بسيرفر، وبيجيب منه سياق للـ host.

الـ server برنامج بيقدّم السياق للكلاينتس.

السيرفرات اللوكال على stdio غالبًا بتخدم كلاينت واحد. الريموت على Streamable HTTP غالبًا بتخدم كتير. كلمة «سيرفر MCP» معناها البرنامج اللي بيقدّم السياق، مش مكان تشغيله. filesystem على نفس الجهاز اسمه لوكال. Sentry على منصته اسمه ريموت.

طبقتين

طبقة الداتا جوه، وطبقة النقل بره. النقل بيخبي تفاصيل الاتصال عشان نفس شكل رسائل JSON-RPC 2.0 يشتغل على كل القنوات.

Data

اكتشاف الإصدار والقدرات، والـ primitives زي tools و resources و prompts، والإشعارات.

Transport

تأسيس الاتصال، تقسيم الرسائل، والأمان. stdio للوكال من غير شبكة. Streamable HTTP للريموت، مع POST و SSE اختياري، و OAuth للتوكنز.

الطرفين بيبعتوا requests ويردوا. الإشعار بيتبعت لما مش محتاج رد.

Stateless

كل ريكوست شايل كل المعلومات اللازمة لمعالجته. السيرفر مش بيستنتج حاجة من اللي قبله.

في حقل _meta: إصدار البروتوكول، وقدرات الريكوست ده. الكلاينت المفروض يعرّف نفسه في نفس الحقل إلا لو متظبط إنه ما يعملش كده. السيرفر يعلن إصداراته وقدراته بـ server/discover، وده إجباري عليه. الكلاينت ممكن يبعته قبل أي ريكوست، ومش لازم.

المفاتيح المهمة جوه _meta:

  • protocolVersion و clientCapabilities إجبارين في كل ريكوست.
  • clientInfo مفروض تتبعت، مش إجباري لو الكلاينت متظبط غير كده.

رد discover غالبًا قابل للكاش: ttlMs مدة الصلاحية بالملي ثانية، و cacheScope مين يقدر يعيد استخدام الرد. التبادل بيختار الإصدار، ويعلن القدرات، ويتبادل الهوية للتصحيح. لو السيرفر مش داعم الإصدار بيرفض بـ UnsupportedProtocolVersionError وفيه الإصدارات اللي يدعمها، والكلاينت يعيد بإصدار مشترك.

discover اختياري للكلاينت لأن كل ريكوست شايل نفس الحقول. هو طريقة مريحة تجيب الهوية والقدرات والإصدارات في ريكوست واحد.

الـ primitives

السيرفر بيقدّم تلاتة: tools دوال يناديها الموديل، resources داتا للقراءة، prompts قوالب تفاعل. كل نوع ليه طريقة list للاكتشاف و get للجلب، وأحيانًا تنفيذ زي tools/call. القوائم ديناميكية.

الكلاينت بيقدّم elicitation: السيرفر يطلب معلومة أو تأكيد من اليوزر بـ elicitation/create عبر نمط MRTR. Sampling و Logging بقوا deprecated من إصدار 2026-07-28. بديل الـ sampling تكامل مباشر مع مزود الموديل. بديل اللوجينج: اكتب على stderr في stdio، أو OpenTelemetry.

فيه امتدادات اختيارية. مثال Tasks: السيرفر يرجّع مقبض دائم للريكوست الطويل، والكلاينت يعمل polling ويجيب النتيجة بعدين.

إشعار لما القائمة تتغير

الإشعارات opt-in. الكلاينت يفتح ستريم بـ subscriptions/listen ويقول أنواع الإشعارات اللي عايزها. السيرفر يرد الأول بـ notifications/subscriptions/acknowledged وفيها subscriptionId، وبعدين يبعت اللي يطابق الفلتر، زي notifications/tools/list_changed.

خمس خصائص: مفيش رد، وبموافقة الطرفين، وكل إشعار متعلم بالـ subscriptionId، والسيرفر يقرر امتى يبعت، والتوصيل best-effort. عشان كده الكلاينت لازم يعتمد كمان على الـ polling، خصوصًا بعد إعادة الاتصال. لما الإشعار يوصل، الكلاينت يعمل tools/list تاني ويحدّث السجل.

المثال باختصار

١. server/discover شايل الإصدار والقدرات في _meta. الرد فيه supportedVersions و capabilities و ttlMs. ٢. tools/list عشان تعرف الأدوات قبل الاستخدام. كل أداة ليها name فريد، و title للواجهة، و description، و inputSchema. ٣. tools/call بالاسم والحجج حسب الـ schema. الرد مصفوفة content (نص، صور، …) وكل عنصر ليه type. ٤. subscriptions/listen بفلتر toolsListChanged لو عايز تحديثات.

ttlMs بقيمة 300000 يعني النتيجة صالحة للكاش خمس دقايق.

خلّصت الفصل لما

🧠 اختبر نفسك
١) لو الـ host متوصل بـ 3 سيرفرات، هيبقى فيه كام كلاينت؟
تلاتة. كلاينت لكل سيرفر، باتصال مخصص.
٢) إيه الحقول اللي بتتحط في _meta، وأنهي مش إجباري؟
protocolVersion و clientCapabilities إجبارين. clientInfo مفروض تتبعت بس مش إجباري.
٣) يعني إيه ttlMs قيمته 300000؟
النتيجة صالحة للكاش خمس دقايق.
٤) ليه الكلاينت يعمل polling حتى لو مشترك في الإشعارات؟
لأن الإشعارات best-effort ومفيش ضمان توصل، خصوصًا مع إعادة الاتصال.
٥) إيه بديل Logging بعد ما اتلغى؟
اكتب على stderr في stdio، أو استخدم OpenTelemetry.