شرح تدفق MCP OAuth 2.1: CIMD مقابل DCR مع أمثلة

تدفق MCP OAuth 2.1 خطوة بخطوة، من تحدي 401 وعمليات البحث عن البيانات الوصفية إلى PKCE والتحقق من التوكن. اطّلع على JSON حقيقي لمستندات بيانات تعريف معرّف العميل وتسجيل العميل الديناميكي، مع جدول مقارنة جنبًا إلى جنب وفحوص الأمان التي يحتاجها كل نهج.

شرح تدفق MCP OAuth 2.1: CIMD مقابل DCR مع أمثلة
Cristian Da Conceicao
مؤسس Picasso IA

يواجه عميل MCP الذي يريد استدعاء خادم محمي مشكلة ثقة منذ أول طلب له. فالخادم لم يلتقِ هذا العميل من قبل، وخادم التفويض الذي يقف خلفه لم يسمع به أيضًا. تحسم مواصفة تفويض MCP هذه المسألة باستخدام OAuth 2.1، والجزء الذي تغيّر أكثر خلال العام الماضي هو كيفية حصول العميل على client_id الخاص به. وصلت مستندات بيانات تعريف معرّف العميل (CIMD) في المراجعة 2025-11-25 بوصفها آلية تسجيل موصى بها، وتعلن المراجعة 2026-07-28 أن تسجيل العميل الديناميكي (DCR) مهجور. تتتبع هذه المقالة تدفق MCP OAuth 2.1 من أول استجابة 401 حتى أول استدعاء أداة مصرّح به، وتعرض طلبات واستجابات حقيقية لمساري التسجيل، وتنتهي بقاعدة بسيطة للاختيار بينهما.

لماذا يحتاج MCP إلى OAuth 2.1

موظفة استقبال في فندق تنزلق بطاقة غرفة بسيطة عبر منضدة رخامية إلى نزيل

تخيّل مكتب استقبال في فندق. تُبرز هويتك مرة واحدة، فيتأكد المكتب من شخصيتك، وتغادر ومعك بطاقة غرفة تفتح غرفتك فقط ولا شيء غيرها. يتبع OAuth النمط نفسه. خادم التفويض هو مكتب الاستقبال، والتوكن هو بطاقة الغرفة، وخادم MCP هو الباب الذي يفحص البطاقة. لا يرى الباب جواز سفرك أبدًا، ولا تفتح بطاقة الغرفة 412 الغرفة 518.

التفويض اختياري في MCP، لكن القواعد تصبح صارمة بمجرد تفعيله. ينبغي لخوادم HTTP SHOULD اتباع مواصفة التفويض، بينما ينبغي لخوادم stdio ألا تفعل ذلك SHOULD NOT، بل تقرأ بيانات الاعتماد من متغيرات البيئة. إليك ما تجعله المواصفة إلزاميًا:

  • PKCE بطريقة S256. يجب على العملاء أيضًا التحقق من أن خادم التفويض يعلن code_challenge_methods_supported، والامتناع عن المتابعة إذا كان الحقل مفقودًا.
  • بيانات تعريف المورد المحمي (RFC 9728). ينشرها خادم MCP، ويستخدمها العميل للعثور على خادم التفويض المناسب.
  • مؤشرات المورد (RFC 8707). يرسل العملاء معامل resource في كل من طلب التفويض وطلب التوكن.
  • توكنات الحامل في الترويسة. تُرفق ترويسة Authorization: Bearer بـكل طلب HTTP، ولا تظهر التوكنات في سلسلة الاستعلام أبدًا.
  • التحقق من الجمهور المستهدف. لا يقبل خادم MCP إلا التوكنات الصادرة له هو.

الأطراف الأربعة

تشمل كل عملية في هذه المقالة الأطراف الأربعة نفسها. احفظ أدوار كل منها في ذهنك وسيصبح الباقي أسهل في الفهم.

الطرفدوره في OAuthمثال نموذجي
المستخدممالك الموردشخص يوافق على الوصول في المتصفح
عميل MCPعميل OAuthتطبيق سطح مكتب للذكاء الاصطناعي، أو بيئة تطوير، أو وكيل سطر أوامر
خادم MCPخادم الموردhttps://mcp.example.com/mcp
خادم التفويضيصدر التوكناتAuth0 أو Okta أو Microsoft Entra ID أو خدمتك الخاصة

💡 قد يعيش خادم MCP وخادم التفويض في نشر واحد، وقد ينتميان إلى شركتين مختلفتين. معرّف العميل لا يحمل معنى إلا لخادم التفويض الذي أصدره أو قبله، لذلك لا ينبغي للعميل أن يفترض أن المعرّف يعمل في كل مكان.

التدفق من 401 حتى التوكن

مطوّر يرتب خمس بطاقات فهرسة مرسوم عليها أسهم بخط اليد على طاولة من خشب البلوط

المصافحة سلسلة قصيرة من طلبات HTTP بسيطة. يمكنك متابعة كل طلب في الطرفية، وهذا يجعل تصحيح الأخطاء أقل غموضًا بكثير مما توحي به الاختصارات.

تحدي 401

يرسل العميل طلب MCP دون توكن. يرفض الخادم الطلب ويخبر العميل أين يبحث:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource",
                         scope="files:read"

معامل scope هو تلميح الخادم حول أدنى صلاحيات مطلوبة لهذا الطلب. إذا غاب معامل resource_metadata، يعود العميل إلى عناوين URL المعروفة، أولًا /.well-known/oauth-protected-resource/mcp (مع إدراج المسار) ثم النسخة الجذرية.

بحثان عن البيانات الوصفية

يجلب العميل مستند بيانات تعريف المورد المحمي ويقرأ منه خادم التفويض الذي يجب استخدامه:

{
  "resource": "https://mcp.example.com/mcp",
  "authorization_servers": ["https://auth.example.com"],
  "scopes_supported": ["files:read", "files:write"]
}

ثم يطلب من خادم التفويض أن يصف نفسه، فيجرّب /.well-known/oauth-authorization-server أولًا، ثم /.well-known/openid-configuration الخاص بمعيار OpenID Connect ثانيًا. يجب أن يطابق issuer الموجود في الاستجابة عنوان URL الذي استخدمه العميل لبناء الطلب، وإلا يُرمى المستند. تبدو الإجابة المعتادة كالتالي:

{
  "issuer": "https://auth.example.com",
  "authorization_endpoint": "https://auth.example.com/authorize",
  "token_endpoint": "https://auth.example.com/token",
  "registration_endpoint": "https://auth.example.com/register",
  "code_challenge_methods_supported": ["S256"],
  "client_id_metadata_document_supported": true,
  "authorization_response_iss_parameter_supported": true
}

يحدد حقلان في هذه الإجابة طريقة تسجيل العميل: client_id_metadata_document_supported (CIMD) وregistration_endpoint (DCR). سنعود إلى كليهما.

PKCE ومعامل المورد

بعد الحصول على client_id، يولّد العميل مُتحقِّق PKCE لمرة واحدة، ويجزّئه، ثم يفتح المتصفح. ويسجّل أيضًا issuer المتوقع ليتحقق منه لاحقًا في الاستجابة.

GET https://auth.example.com/authorize?response_type=code
  &client_id=https%3A%2F%2Fapp.example.com%2Foauth%2Fclient-metadata.json
  &redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback
  &code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
  &code_challenge_method=S256
  &resource=https%3A%2F%2Fmcp.example.com%2Fmcp
  &scope=files%3Aread
  &state=xyz123

القيمة resource هي المعرّف الموحّد القياسي لخادم MCP. يجب على العملاء MUST إرسالها حتى إن تجاهلها خادم التفويض، لأنها ما يتيح ربط التوكن بخادم محدد بعينه.

تبادل الرمز واستخدام التوكن

يوافق المستخدم، فيعود المتصفح إلى عنوان إعادة التوجيه ومعه code وstate وغالبًا معامل iss. تضيف المراجعة 2026-07-28 فحص المُصدِر من RFC 9207: إذا كان iss موجودًا، يقارنه العميل بالمُصدِر المسجّل قبل إرسال الرمز إلى أي جهة. هذا يمنع هجمات الخلط، حيث يحاول خادم تفويض معادٍ انتزاع رموز مخصصة لخادم أمين.

ثم يأتي طلب التوكن، الذي يثبت امتلاك مُتحقِّق PKCE:

POST /token HTTP/1.1
Host: auth.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fcallback
&client_id=https%3A%2F%2Fapp.example.com%2Foauth%2Fclient-metadata.json
&code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
&resource=https%3A%2F%2Fmcp.example.com%2Fmcp

تحمل الاستجابة توكن الوصول، ومن تلك اللحظة يتضمن كل طلب إلى خادم MCP Authorization: Bearer <access-token>. وإذا نقص نطاق في التوكن لاحقًا، يرد الخادم بـ403 مع error="insufficient_scope"، ويعيد العميل التفويض باستخدام اتحاد النطاقات القديمة والجديدة.

DCR: كيف يعمل، وأين يتعثر

مسافرون يصطفون أمام صف من بوابات مطارات إلكترونية متطابقة تحت سقف زجاجي

يأتي تسجيل العميل الديناميكي من RFC 7591. الفكرة بسيطة: قبل أول تسجيل دخول، يرسل العميل بياناته إلى registration_endpoint ويحصل في المقابل على client_id جديد. لا يملأ أي إنسان نموذجًا. ولسنوات كان هذا الطريق الرئيسي لتسجيل عميل مجهول تلقائيًا، ولهذا تبنّاه MCP في مراحله الأولى.

طلب التسجيل

إليك تبادلًا واقعيًا لعميل MCP على سطح المكتب:

POST /register HTTP/1.1
Host: auth.example.com
Content-Type: application/json

{
  "client_name": "Example MCP Client",
  "redirect_uris": ["http://localhost:3000/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none",
  "application_type": "native"
}
{
  "client_id": "s6BhdRkqt3",
  "client_name": "Example MCP Client",
  "redirect_uris": ["http://localhost:3000/callback"],
  "grant_types": ["authorization_code", "refresh_token"],
  "token_endpoint_auth_method": "none",
  "client_id_issued_at": 1791247700
}

لاحظ application_type. منذ المراجعة 2026-07-28، يجب على العملاء MUST ضبطه. وخوادم OpenID Connect التي تتبع هذا المعيار تعامل القيمة المفقودة على أنها web، وقد يرفض هذا الافتراض عناوين إعادة توجيه localhost.

لماذا تعاني الخوادم منه

يعمل DCR، لكنه يحمّل خادم التفويض عبئًا كبيرًا:

  • نقطة نهاية كتابة عامة. يمكن لأي شخص على الإنترنت إنشاء سجلات، لذلك تحتاج إلى حدود للمعدل، ومدة صلاحية، ومهام تنظيف.
  • تسجيل واحد لكل زوج. يسجّل كل عميل نفسه بشكل منفصل لدى كل خادم تفويض، ويجب أن يحفظ النتيجة بأمان، مفهرسة حسب issuer. وعندما يتغير خادم التفويض، يجب على العميل التسجيل من جديد.
  • أسماء لم يتحقق منها أحد. تعرض شاشة الموافقة أي client_name كتبه المسجِّل، لذلك يمكن لتطبيق خبيث أن يسمّي نفسه بما يشاء.
  • نمو قاعدة البيانات. تتحول آلاف عمليات تثبيت عميل واحد شائع إلى آلاف السجلات التي تعني كلها الشيء نفسه.

هذه التكاليف هي سبب توجيه المواصفة الآن التطبيقات الجديدة إلى مسار آخر.

CIMD: عنوان URL هو معرّف العميل

ضابط حدود يفحص صفحة جواز سفر تحت مصباح مكتب بعدسة مكبّرة

فكّر في جواز السفر. لا أحد يطلب من ضابط الحدود أن يحفظك مسبقًا. تقدّم وثيقة، ويتحقق الضابط منها لدى الجهة التي أصدرتها. يقلب CIMD التسجيل بالطريقة نفسها: ينشر العميل مستند JSON على عنوان HTTPS ثابت، وهذا العنوان هو client_id. يقرأ خادم التفويض المستند عندما يرى العنوان لأول مرة، فلا يوجد شيء يُسجَّل مسبقًا.

مستند البيانات الوصفية

قواعد العميل قصيرة. يجب أن يستخدم client_id المخطط https وأن يتضمن مسارًا، ويجب أن يحتوي المستند على client_id وclient_name وredirect_uris، ويجب أن يطابق client_id داخل الملف عنوان URL الذي قُدِّم منه حرفًا بحرف. ومثال المواصفة نفسه يبدو هكذا:

{
  "client_id": "https://app.example.com/oauth/client-metadata.json",
  "client_name": "Example MCP Client",
  "client_uri": "https://app.example.com",
  "logo_uri": "https://app.example.com/logo.png",
  "redirect_uris": [
    "http://127.0.0.1:3000/callback",
    "http://localhost:3000/callback"
  ],
  "grant_types": ["authorization_code"],
  "response_types": ["code"],
  "token_endpoint_auth_method": "none"
}

ما الذي يتحقق منه الخادم

عندما يصل طلب تفويض بعنوان URL بالشكل client_id، يمر خادم التفويض بإجراء ثابت:

  1. اجلب المستند باستخدام طلب HTTPS GET عادي.
  2. تأكد من أنه JSON صالح ويحتوي على الحقول المطلوبة.
  3. تأكد من أن client_id في الملف يطابق عنوان URL تمامًا.
  4. تأكد من أن redirect_uri في الطلب يطابق أحد العناوين المدرجة في الملف.
  5. خزّن النتيجة مؤقتًا مع احترام ترويسات التخزين المؤقت في HTTP.
  6. اعرض للمستخدم client_name واسم مضيف إعادة التوجيه على شاشة الموافقة.

نموذج مبسّط للخطوات من 1 إلى 3 بلغة TypeScript، مكتوب من أجل الوضوح لا للإنتاج:

async function loadClient(clientId: string) {
  const url = new URL(clientId);
  if (url.protocol !== "https:" || url.pathname === "/") throw new Error("invalid_client");
  await assertPublicHost(url.hostname); // reject private, loopback and link-local addresses

  const res = await fetch(url, { redirect: "error", signal: AbortSignal.timeout(5000) });
  const doc = await res.json();

  if (doc.client_id !== clientId) throw new Error("invalid_client");
  if (!doc.client_name || !Array.isArray(doc.redirect_uris)) throw new Error("invalid_client");
  return doc;
}

الإعلان عن دعم CIMD

يعلن خادم التفويض هذه الميزة في بيانات تعريفه باستخدام "client_id_metadata_document_supported": true. يستخدم العملاء الذين يجدونها عنوان URL الخاص بهم بوصفه client_id ويتجاوزون التسجيل كليًا. ولأن المعرّف عنوان URL عام، فهو قابل للنقل أيضًا: يستطيع العميل نفسه التحدث إلى خادم تفويض مختلف غدًا دون تسجيل جديد.

CIMD مقابل DCR جنبًا إلى جنب

بابان متطابقان في ممر من الطوب، أحدهما بقفل رقمي والآخر بلوحة اسم

السؤالCIMDDCR
الحالة في المواصفة 2026-07-28موصى به (SHOULD)مهجور، ومُبقى للتوافق (MAY)
من يخزن سجل العميلالعميل يستضيفه، والخادم يخزنه مؤقتًاخادم التفويض هو من يخزنه
شكل client_idعنوان URL مثل https://app.example.com/oauth/client-metadata.jsonسلسلة غير شفافة مثل s6BhdRkqt3
يحتاج إلى نقطة تسجيللانعم، ويُعلن عنها باسم registration_endpoint
العمل قبل أول تسجيل دخوللا شيء على العميلطلب POST واحد لكل خادم تفويض
القابلية للنقل بين خوادم التفويضنعملا، يجب التسجيل من جديد لكل مُصدِر
الخطر الرئيسيSSRF أثناء الجلب، وانتحال localhostإساءة استخدام نقطة مفتوحة، وسجلات غير مرغوب فيها
كيف يعلن الخادم عنهclient_id_metadata_document_supportedregistration_endpoint

يجب على العميل الذي يدعم كل الخيارات SHOULD أن يختار بهذا الترتيب:

  1. استخدام بيانات العميل المسجّلة مسبقًا إذا كانت لديه لهذا الخادم.
  2. استخدام CIMD إذا أعلن خادم التفويض دعمه.
  3. الرجوع إلى DCR إذا وُجد registration_endpoint.
  4. مطالبة المستخدم بكتابة بيانات العميل يدويًا.

💡 يبقى التسجيل المسبق الأفضل متى توفّر. إذا كنت تتحكم في العميل وخادم التفويض معًا، فإن client_id ثابتًا يتجاوز كل عملية بحث مذكورة أعلاه.

فحوص الأمان التي لا يمكنك تخطيها

قفل نحاسي وسلسلة فولاذية على بوابة حديدية قديمة عند الفجر

الانتقال من DCR إلى CIMD لا يزيل المخاطر. إنه ينقلها إلى أماكن مختلفة، ولكل منها مسؤول يتولاها.

SSRF أثناء الجلب

مع CIMD، يقرر زائر مجهول أي عنوان URL يطلبه خادمك. إذا وجّهت client_id إلى https://169.254.169.254/latest/meta-data/ أو إلى لوحة إدارة داخلية، يتحول الجالب المهمل إلى وكيل يصل إلى شبكتك. حلّ اسم المضيف أولًا، وارفض نطاقات العناوين الخاصة، وعناوين loopback وlink-local. اضبط مهلة قصيرة، وحدد حجم الاستجابة الأقصى، وكن صارمًا في إعادة التوجيه.

إعادة التوجيه إلى localhost

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

الجمهور المستهدف ومرور التوكن

فني يفحص خزانة خوادم في ممر مركز بيانات بمصباح يدوي

يجب أن يكون خادم MCP خط الدفاع الأخير، وعليه أن يفحص البطاقة فعليًا لا أن يلقي عليها نظرة عابرة. تحقق من التوقيع، وتاريخ الانتهاء، والنطاقات، والأهم من ذلك الجمهور المستهدف (audience). يجب رفض أي توكن أُصدر لخدمة أخرى برمز 401. وإذا كان خادم MCP يستدعي API في المنبع، فهو يحتاج إلى توكن منفصل لتلك الـ API، يصدره خادم التفويض الخاص بها. أما تمرير توكن العميل كما هو إلى الخدمة التالية فيُسمى token passthrough، وتحظره المواصفة صراحةً.

قائمة قصيرة للتحقق منها، تثبّتها بجوار شاشتك:

  • قدّم كل نقاط نهاية التفويض عبر HTTPS، ولا تسمح بإعادة التوجيه إلا عبر HTTPS أو localhost.
  • اجعل توكنات الوصول قصيرة العمر، ودوّر توكنات التحديث للعملاء العامين.
  • استخدم معامل state وتحقق منه.
  • تحقق من iss عندما يكون موجودًا، قبل استبدال الرمز.
  • ضمّن كل النطاقات اللازمة لعملية واحدة في تحدٍ واحد، حتى لا يُرسَل المستخدم عبر شاشات موافقة متكررة.

اختيار الاستراتيجية

ثلاثة مهندسين يرسمون صناديق وأسهمًا على لوح أبيض زجاجي

القرار أقل إثارة مما توحي به النقاشات. ادعم CIMD أولًا، واحتفظ بتسجيل DCR جسرًا، وكن صادقًا بشأن الدور الذي تلعبه فعلًا.

إذا كنت تشغّل خادم MCP

انشر بيانات تعريف المورد المحمي في كل الأحوال. اختر خادم تفويض يدعم CIMD، وإذا لم يكن خادمك يدعمه بعد، فأبقِ DCR مفعّلًا مع حدود للمعدل وانتهاء صلاحية بدلًا من حظر كل العملاء. ضع scope في تحدي WWW-Authenticate الخاص بك، واستجب بـ403 وinsufficient_scope عندما يكون التوكن غير كافٍ، وتحقق من الجمهور المستهدف في كل طلب.

إذا كنت تبني عميل MCP

استضف مستند البيانات الوصفية الخاص بك على عنوان URL ستحتفظ به لسنوات، لأن هذا العنوان هو هويتك. اقرأ بيانات تعريف خادم التفويض، ثم اختر مسار التسجيل في الكود:

function pickRegistration(as: AuthServerMetadata, saved?: SavedClient) {
  if (saved && saved.issuer === as.issuer) return { mode: "saved", clientId: saved.clientId };
  if (as.client_id_metadata_document_supported) return { mode: "cimd", clientId: CLIENT_METADATA_URL };
  if (as.registration_endpoint) return { mode: "dcr", endpoint: as.registration_endpoint };
  return { mode: "manual" };
}

عندما تلجأ إلى DCR فعلًا، خزّن بيانات الاعتماد مرتبطة بـissuer، واضبط application_type: "native" لتطبيقات سطح المكتب وسطر الأوامر، ولا تعِد استخدامها مع خادم تفويض آخر أبدًا.

جرّب ذلك على PicassoIA

مصمم يحمل صورة فوتوغرافية مطبوعة أمام نافذة استوديو علوي

أيًا كان الجانب الذي تبنيه من المصافحة، ستكتب كثيرًا من JSON وملفات الاختبار والتوثيق. يسرّع نموذج لغوي كبير قادر هذا العمل، وتستضيف PicassoIA عدة نماذج منه. إليك طريقة سريعة لاستخدام Claude Sonnet 5 مراجعًا لمستند البيانات الوصفية الخاص بك:

  1. افتح صفحة Claude Sonnet 5 على PicassoIA.
  2. الصق client-metadata.json وبيانات تعريف خادم التفويض من مزوّدك.
  3. اطلب تشغيل قائمة تحقق: "تحقق من هذا المستند مقابل قواعد CIMD: يساوي client_id عنوان URL، وhttps مع مسار، والحقول المطلوبة موجودة، وredirect_uris مطابقة تمامًا. اذكر كل إخفاق."
  4. اطلب أن يكون الناتج جدولًا من القاعدة والنتيجة والإصلاح، ليسهل لصقه في طلب الدمج.
  5. للحصول على رأي ثانٍ، شغّل الأمر نفسه على GPT 5.6 Sol أو مرّ سريعًا عبر Gemini 3.5 Flash، وقارن الإخفاقات التي يجدها كل واحد.

💡 تراجع النماذج المستندات جيدًا، لكنها لا تحل محل اختبار حقيقي. شغّل تدفقك على خادم تفويض تجريبي قبل النشر.

تحتاج الوثائق إلى الصور بقدر حاجتها إلى JSON. فصورة رئيسية أو خلفية لمخطط أو بطاقة للتواصل الاجتماعي تجعل المنشور عن OAuth أسهل في المشاركة، ومنصة PicassoIA مصممة تحديدًا لهذا الغرض. تأتي النتائج الواقعية كالصور الفوتوغرافية من أوامر نصية محددة: سمِّ العدسة، واتجاه الضوء، والملمس في المشهد. جرّب شيئًا مثل "a hotel receptionist sliding a room card across a marble counter, 50mm lens, soft window light from the right, film grain" وانظر ما الذي ستحصل عليه. كما يمكن للمطورين الوصول إلى نماذج الصور والفيديو في PicassoIA عبر PicassoIA API واتصالات MCP، فيمكن تشغيل الأمر النصي نفسه من وكيلك الخاص.

هل أنت مستعد لإنشاء صورك الخاصة؟ افتح PicassoIA، واختر نموذجًا، وابدأ تجربة أول أمر نصي لك اليوم.

شارك هذا المقال

اختر لغتك

مقالات ذات صلة