تخطَّ إلى المحتوى
27/30الفصل 27 من 30

إطلاق خادم MCP: TypeScript وPython بالأرقام

الخادم نفسه مكتوب مرتين: ثلاث أدوات ومورد وprompt، ثم قياسه: 94 حزمة مقابل 28، وبدء بارد 145 ms مقابل 709.

في هذه الصفحة

هذه هي حجة اللغة كاملة، مقاسة، قبل أن تُصاغ كلمة واحدة منها.

spawn → tools/list answered, median of 25 launchesTEXT
node ./incidents.js         144.5 ms
python incidents.py         709.4 ms
npx incidents-mcp           712.6 ms

السطران الأولان هما المقارنة التي يريدها الجميع. السطر الثالث هو خادم TypeScript نفسه من السطر الأول، مُشغّلًا بالطريقة التي سيُوزَّع بها فعليًا — ويصل على بُعد ثلاث ميلي ثوانٍ من Python.

قرأ الفصل 26 Model Context Protocol مقابل مواصفته نفسها عبر JSON-RPC خام، لأن JSON-RPC الخام لا يملك لغة. هذا الفصل يملك لغتين، وثقل الحجة يقع هنا: الخادم نفسه، مكتوب مرتين. ثلاث أدوات، مورد واحد، prompt واحد، كلا SDKين، بلا اختصارات في أي طرف. ثم وسائل النقل، وInspector، و401، والأرقام التي لم ينشرها أحد.

الخادم، ولماذا يحتوي على هذه الأشياء الخمسة

رابط إلى القسم: الخادم، ولماذا يحتوي على هذه الأشياء الخمسة

سجل حوادث. ثلاث أدوات، لأن فصل الفصل 18 بين القراءة والكتابة يجب أن يكون مرئيًا: search_incidents يقرأ، وopen_incident يكتب ويعيد handle، وresolve_incident يأخذ ذلك handle ويغلق. مورد واحد، incidents://open، لأن قراءة القائمة الحالية شيء تُلحقه التطبيقات. وprompt واحد، postmortem، لأن «اكتب هذا بصيغة تقرير» هو أمر slash لشخص. ذلك هو تسلسل التحكم في الفصل 26 — model، application، person — وقد تحوّل إلى خمسة تسجيلات.

الـhandle أهم مما يبدو. كسر الفصل 26 تقويمًا بسيطًا لأنه أبقى حالته في مصفوفة على مستوى module: البروتوكول لا يملك session، لذلك تعيد أداة الإنشاء معرفًا opaque وكل استدعاء لاحق يأخذه كوسيط عادي. لا شيء في أي من الملفين يفترض أن المستدعي هو العملية التي فتحته.

إليك الأداة نفسها في اللغتين، مسجلة جنبًا إلى جنب:

incidents.tsTS
server.registerTool(
  "resolve_incident",
  {
    description:
      "Close an incident by handle and record its cause.",
    inputSchema: {
      id: z.string().describe(
        "The handle returned by open_incident, e.g. INC-3."),
      cause: z.string().describe(
        "One sentence. What actually broke."),
    },
    annotations: {
      readOnlyHint: false,
      destructiveHint: true,
      idempotentHint: true,
    },
  },
  async ({ id, cause }) => {
    const at = OPEN.findIndex((i) => i.id === id);
    if (at < 0) {
      return { isError: true, content: [{ type: "text",
        text: `No open incident ${id}. ` +
              `Call search_incidents first.` }] };
    }
    const [done] = OPEN.splice(at, 1);
    return { content: [{ type: "text",
      text: JSON.stringify({ ...done, cause }) }] };
  },
);
incidents.pyPYTHON
@server.tool(
    description=
      "Close an incident by handle and record its cause.",
    annotations=ToolAnnotations(
        readOnlyHint=False,
        destructiveHint=True,
        idempotentHint=True,
    ),
)
def resolve_incident(
    id: Annotated[str, Field(description=
        "The handle returned by open_incident, e.g. INC-3.")],
    cause: Annotated[str, Field(description=
        "One sentence. What actually broke.")],
) -> Incident:
    for at, i in enumerate(OPEN):
        if i["id"] == id:
            done = OPEN.pop(at)
            return {**done, "cause": cause}
    raise ValueError(
        f"No open incident {id}. Call search_incidents first.")

اقرأ ما هو متماثل أولًا، لأن هذه هي النتيجة. كلاهما يعلن اسمًا ووصفًا ووسيطين نصيين موصوفين وثلاث annotations؛ كلاهما function واحدة؛ ولا يذكر أي منهما JSON-RPC أو framing أو stdout أو إصدار بروتوكول. تقارب SDKان على الشكل نفسه، وهذا ما يفترض أن يعنيه «Tier 1».1

هناك فرقان حقيقيان وكلاهما يعود لاحقًا. يصف TypeScript الوسائط بمكتبة schema — Zod هنا — والـschema قيمة تكتبها. أما Python فيصفها عبر type hints الخاصة بالfunction نفسها ويقرأها وقت الاستيراد، ولهذا يعرف أشياء عن الfunction لم يخبره بها ملف TypeScript قط. ومسار الخطأ: يعيد TypeScript نتيجة أداة مع isError، بينما يرفع Python استثناء. احتفظ بهذه النقطة.

التسجيلات الأربعة الأخرى لا تختلف في أي بنية. المورد هو server.registerResource("open-incidents", "incidents://open", …) مقابل @server.resource("incidents://open", …)؛ والـprompt هو registerPrompt مقابل @server.prompt. السطر الأخير في كل ملف هو النقل: await server.connect(new StdioServerTransport()) مقابل server.run().

الملفان كاملان: 81 سطرًا غير فارغ و3,060 بايت من TypeScript مقابل 63 و2,555. خذ ذلك بالقدر المستحق من التحفظ — عدد الأسطر يقيس formatter بقدر ما يقيس لغة، ولهذا لا يظهر أي من الرقمين في جدول العناوين أدناه.

الدليل على أن اللغة غير مرئية هو تشغيل عميل واحد مرتين، في أحد عشر سطرًا:

client.tsTS
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const client = new Client({ name: "incident-cli", version: "1.0.0" });
await client.connect(new StdioClientTransport({
  command: process.argv[2], args: process.argv.slice(3) }));

const { tools } = await client.listTools();
console.log("tools:", tools.map((t) => t.name).join(", "));

const opened = await client.callTool({ name: "open_incident",
  arguments: { title: "Queue backed up", severity: "sev2" } });
console.log("open_incident ->", JSON.stringify(opened.content));

وجّهه إلى كل خادم بالتتابع. مخرجات حقيقية، مختصرة:

TEXT
$ node client.ts node incidents.ts
tools: search_incidents, open_incident, resolve_incident
open_incident -> [{"type":"text","text":"{\"id\":\"INC-3\"}"}]

$ node client.ts ./py/.venv/bin/python ./py/incidents.py
tools: search_incidents, open_incident, resolve_incident
open_incident -> [{"type":"text","text":"{\n  \"id\": \"INC-3\"\n}"}]

الأدوات نفسها، الترتيب نفسه، الـhandle نفسه. لا يستطيع عميل TypeScript أن يعرف بماذا كُتب الخادم، ولا يسأل أبدًا. هذا هو وعد البروتوكول كاملًا، وقد صمد.

انظر الآن إلى المسافات البيضاء في النتيجة الثانية، لأنها ليست تجميلية: SDK الخاص بـPython يسلسل payloads باستخدام pydantic_core.to_json(result, fallback=str, indent=2). عند قراءة المورد وفي القائمة حادثتان، يبلغ جسم TypeScript ‏136 حرفًا و37 token من o200k_base؛ أما جسم Python فـ185 و62. tokens أكثر بنسبة ثمانية وستين في المئة للصفوف نفسها، يدفع ثمنها كل من يقرأ المورد داخل prompt، كل مرة.

يحكي الفهرس القصة نفسها ولكن بسبب أكبر. كلا الخادمين، الأدوات الثلاث نفسها، وtools/list موزون مفتاحًا بمفتاح:

المفتاحTypeScriptPython
name2121
description4646
annotations4646
inputSchema211192
outputSchema187
execution27
الإجمالي342480

Schemas الإدخال في Python أرخص — جسر Zod في TypeScript يطبع $schema وadditionalProperties على كل واحدة. الفجوة الكاملة البالغة 138 token هي schema خرج لم يكتبها أحد. resolve_incident معلّمة بـ-> Incident، لذلك اشتق SDK ‏JSON Schema لنوع الإرجاع وشحنها. هذا مفيد فعلًا — فهو ما يتيح للعميل التحقق من structuredContent — لكنه 187 token من context window تصل لأن هناك type hint. تنطبق قاعدة الفصل 24 عن مزاحمة التعريفات للمادة المهمة على schemas لم تكن تعرف أنك تملكها.

اكسره عمدًا: رسالة الخطأ التي تسرّبت

رابط إلى القسم: اكسره عمدًا: رسالة الخطأ التي تسرّبت

مسارا الخطأ أعلاه ليسا خيار أسلوب. أعطِ كل خادم أداة تفشل كما تفشل integration حقيقية، واقرأ ما يصل إلى model.

tools/call on a tool that raisesTEXT
TypeScript  {"content":[{"type":"text","text":
              "connect ECONNREFUSED 10.0.3.7:5432 (db-prod-eu, user=reporting)"}],
             "isError":true}

Python      {"content":[{"text":"Error executing tool boom","type":"text"}],
             "isError":true}

وضع SDK الخاص بـTypeScript عنوانًا داخليًا ومنفذًا واسم قاعدة بيانات وحساب خدمة داخل سياق model. أما SDK الخاص بـPython فلم يضع شيئًا من ذلك هناك؛ ذهب traceback إلى stderr وبقي على الخادم.

ليس أي منهما bug. كلاهما قرار، وقرار Python مكتوب في docstring الخاصة به: إن ToolError هو «فشل توقّعته» وتُعاد رسالته «في content ليقرأها model»؛ وأي شيء آخر «يُعامل كانهيار: لا يرى model إلا Error executing tool <name>، ويسجل الخادم traceback عند ERROR». وتقول class الخاصة بحالة الانهيار الباقي صراحة — «لا يصل شيء من الأصل إلى العميل».

كلا السلوكين خاطئ نصف الوقت. جادل الفصل 18 بأن خطأ التحقق ينبغي أن يعود كنتيجة أداة يمكن لـmodel قراءتها وتصحيحها، لأن ذلك هو السطر الأعلى أثرًا في معظم integrations؛ على جانب Python يتطلب ذلك رفع ToolError صراحة، وValueError عارٍ يرمي الجملة المفيدة بعيدًا. تسير حجة الفصل 30 في الاتجاه المعاكس: كل ما تعيده أداة يهبط في سياق يستطيع prompt injection لاحقًا محاولة قراءته من جديد، ونص استثناء غير مُراجع هو أقل نص خاضع للتدقيق في نظامك.

القاعدة التي تنجو من الاثنين: قرّر، لكل أداة، ما الذي يُسمح للفشل أن يقوله، واكتب تلك السلسلة بنفسك. لا تدع النص الافتراضي للاستثناء يقرر، في أي لغة.

اكسره عمدًا: سطر واحد على standard output

رابط إلى القسم: اكسره عمدًا: سطر واحد على standard output

تذكر الدليل الرسمي القاعدة بلا مواربة: «للخوادم القائمة على STDIO: لا تكتب إلى stdout أبدًا. الكتابة إلى stdout ستفسد رسائل JSON-RPC وتكسر خادمك. الدالة print() تكتب إلى stdout افتراضيًا، لذلك أبقِها خارج خادم STDIO تمامًا.»1 اقتبس الفصل 26 النسخة المعيارية — الخادم «MUST NOT write anything to its stdout that is not a valid MCP message».2

أضف سطرًا واحدًا إلى كل خادم واقرأ stream الخام:

raw stdout, first two linesTEXT
TypeScript  incidents server starting
            {"result":{"protocolVersion":"2025-11-25", … },"jsonrpc":"2.0","id":1}

Python      {"jsonrpc":"2.0","id":1,"result":{ … }}
            incidents server starting

نسخة Python أسوأ، والسبب ليس MCP. العملية التي يكون stdout لديها pipe بدل terminal تحصل على stream ذي block-buffering، لذلك يُflush السطر الشارد عندما يقرر buffer — هنا، عند الخروج، بعد رد كُتب قبله. لا يظهر الفساد في المكان الذي توجد فيه bug. أضف flush=True، أو مكتبة تعمل flush، فينتقل.

ثم الجزء الذي يفسر لماذا يصل هذا إلى الإنتاج. أرسل الخادم المكسور إلى ثلاثة عملاء:

TEXT
naive parser, dirty server   SyntaxError: Unexpected token 'i',
                             "incidents "... is not valid JSON
SDK client, dirty server     tools: search_incidents, open_incident, resolve_incident
MCP Inspector, dirty server  full catalogue, no warning

يموت parser ذو السبعة أسطر فورًا. أما العميل الرسمي وInspector فيتجاهلان الأمر — يتخطيان السطر ويتابعان. القاعدة التي لا تكسر إلا العملاء الذين لا يستخدمهم أحد هي قاعدة تصل إلى الإنتاج سليمة، ولهذا يستحق الأمر كسرها عمدًا هنا بدلًا من سجل عميل.

وضع CLI في Inspector هو النصف الذي يُنسى: npx @modelcontextprotocol/inspector --cli <command> --method tools/list يطبع فهرسًا ويخرج، ما يجعله قابلًا للبرمجة بطريقة لا تتيحها واجهة المتصفح.3

تثبّت كلا SDKين نظيفًا، كل في دليله، بلا أي شيء مشترك:

TypeScriptPython
الحزمة@modelcontextprotocol/sdk 1.30.0 + zod 3.25.76mcp 2.1.1
أحدث مراجعة بروتوكول منفّذة2025-11-252026-07-28
الحزم المتعدية المثبتة9428
الحجم المثبت13.9 MiB44.3 MiB
الملفات على القرص3,3862,018
حزم الطرف الثالث المحمّلة لخدمة stdio8 من 9418 من 28
بدء المفسر العاري، الوسيط19.4 ms11.1 ms
spawn → تمت إجابة tools/list، وسيط 25 مرة144.5 ms709.4 ms
فهرس tools/list، tokens o200k_base342480

كل صف يفاجئ في اتجاه مختلف، ولهذا تستحق المقارنة أن تُجرى بدل افتراضها.

يثبّت TypeScript أكثر من ثلاثة أضعاف عدد الحزم وبأقل من ثلث البايتات. ‏94 dependency هي منظومة npm وهي تتصرف على طبيعتها — fast-deep-equal، es-errors، dunder-proto. أما 28 الخاصة بـPython فهي أقل عددًا وضخمة: cryptography وpydantic-core وuvicorn artifacts مترجمة. إن كان حدسك أن عدد dependencies هو ما ينبغي القلق منه، فهذا الصف هو المثال المضاد.

يبدأ مفسر Python أسرع من Node، وبفارق واضح — 11.1 ms مقابل 19.4 ms على برنامج فارغ. لذلك فالـ565 ms في صف cold-start ليست اللغة. إنها SDK، ويقول صف الحزم المحمّلة السبب:

third-party modules loaded to answer one tools/list over stdioTEXT
TypeScript   8 of 94   sdk, zod, zod-to-json-schema, ajv, ajv-formats,
                       fast-deep-equal, fast-uri, json-schema-traverse

Python      18 of 28   mcp, mcp_types, pydantic, pydantic_core, anyio,
                       starlette, uvicorn, sse_starlette, httpx2,
                       cryptography, _cffi_backend, opentelemetry, click, …

خادم لا يملك من I/O إلا pipe يستورد خادم ويب ASGI وعميل HTTP ومكتبة TLS قبل أن يقرأ أول سطر. يشحن SDK الخاص بـTypeScript أيضًا Express وHono وjose وeventsource — لكنها تبقى على القرص غير مقروءة، لأن حدود الحزمة تبقيها خارج import ‏server/stdio.js. حزمة Python عبارة عن import graph واحد، لذلك import mcp هو كل شيء: ينسب python -X importtime ‏727 ms إلى import mcp.server.mcpserver — رقم مقاس تحت import profiler، ولهذا يأتي أعلى من 709 ms التي تستغرقها الجولة غير المراقبة من spawn حتى الإجابة — و269 منها إلى subtree ‏mcp.types وحدها — أنواع wire هي models من Pydantic، class واحد لكل رسالة بروتوكول لكل revision، وبناؤها عمل يُنجز وقت الاستيراد. هذه مقايضة تصميم، لا إهمال — imports المبكرة هي سبب قدرة SDK الخاص بـPython على إعطائك run(transport="streamable-http") في السطر التالي بلا تثبيت ثانٍ.

ثم يأتي الصف الأخير من الكتلة الافتتاحية لينقض الحجة. حزّم خادم TypeScript كما ينبغي — مدخل bin، وshebang، وnpm link، ولا شيء يُحمّل — وشغّله عبر npx مع --no-install، وهي الطريقة التي يبدأ بها خادم stdio منشور فعليًا:

median of 25, spawn → tools/list answeredTEXT
node ./incidents.js       144.5 ms
npx incidents-mcp         712.6 ms      (+568.1 ms of launcher)
python incidents.py       709.4 ms

يكلف launcher ‏568 ms في كل بدء — أربعة أضعاف ونصف كامل import الخاص بـSDK ‏TypeScript — وتُدفع في كل تشغيل، لأن مضيف MCP يبدأ خادم stdio بتشغيل ذلك الأمر. لذلك فالصيغة الصادقة لعبارة «TypeScript يبدأ أسرع بخمس مرات» هي: نعم، إلى أن توزّعه بالطريقة المعتادة. ينطبق التحفظ نفسه على الأرجح على uvx؛ لم يكن على هذا الجهاز uv مثبتًا، لذلك لا يوجد ذلك الصف. لا يدخل الجدول شيء غير مقاس.

غطى الفصل 26 framing الخاص بـstdio. ترك شيئين لهذا الموضع.

الأول: تشغيل خادم باستخدام npx أو uvx هو نقل stdio. لا يوجد «وضع حزمة» منفصل. تسمّي إعدادات المضيف command وarguments؛ يشغّله المضيف ويتحدث عبر pipes. ولهذا فإن «كيف أوزع هذا» و«أي transport يتكلم» هما سؤال واحد محليًا، ولهذا تنتمي تكلفة launcher إلى فصل عن الشحن.

الثاني: لا يملك stdio قسم تفويض إطلاقًا، وتقول المواصفة ذلك في سطر واحد — implementations التي تستخدم stdio «SHOULD NOT follow this specification, and instead retrieve credentials from the environment».4 نموذجه الأمني هو نموذج نظام التشغيل، وكذلك حدّه: subprocess محلي يخدم جهازًا واحدًا ومستخدمًا واحدًا.

النقل الحي الآخر هو Streamable HTTP: endpoint واحد يقبل POST، وطلب HTTP واحد لكل رسالة JSON-RPC، وheader ‏Accept يجب أن يدرج كلًا من application/json وtext/event-stream لأن الخادم يختار لكل طلب أيهما يجيب به.5 حلّل الفصل 14 ذلك event stream يدويًا، لذلك لا جديد في wire format — الجديد فقط ما يغلّفه. ثلاث التزامات في المراجعة الحالية يسهل تفويتها وكلها قابلة للاختبار:

يجب أن يتفق header الإصدار مع الجسم

رابط إلى القسم: يجب أن يتفق header الإصدار مع الجسم

يحمل كل POST ‏MCP-Protocol-Version، ويجب أن تطابق قيمته protocolVersion داخل _meta الخاصة بالطلب نفسه. عدم التطابق هو 400 مع خطأ header-mismatch، لا تجاهل.5

Mcp-Method يعكس method في كل طلب؛ وMcp-Name يعكس params.name أو params.uri على tools/call وresources/read وprompts/get. وُجدا حتى يستطيع proxy التوجيه بلا parsing للأجسام.5

الأشكال القديمة اختفت، وتجيب بالرفض

رابط إلى القسم: الأشكال القديمة اختفت، وتجيب بالرفض

أُزيلت كل من GET stream وMcp-Session-Id وLast-Event-ID resumption. ينبغي لخادم لا يتكلم إلا هذه المراجعة أن يجيب 405 Method Not Allowed على GET أو DELETE، ويتجاهل session header بلا minting لواحد، ويتجاهل Last-Event-ID.5

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

POST /mcp, MCP-Protocol-Version: 2026-07-28TEXT
Python   200  {"result":{"resultType":"complete","cacheScope":"private","ttlMs":0,
              "tools":[…],"_meta":{"io.modelcontextprotocol/serverInfo":{…}}}}

TypeScript    {"error":{"code":-32000,"message":"Bad Request: Unsupported protocol
              version: 2026-07-28 (supported versions: 2025-11-25, 2025-06-18,
              2025-03-26, 2024-11-05, 2024-10-07)"}}

تتفق الثوابت مع السلوك: يقرأ LATEST_PROTOCOL_VERSION في SDK الخاص بـPython ‏2026-07-28، ويقرأ الخاص بـTypeScript ‏2025-11-25. أرسل طلب header-mismatch من الخطوة أعلاه فيجيب خادم Python بـ400 مع الخطأ -32020 والرسالة «mcp-protocol-version header does not match the request envelope's protocol version»؛ أما SDK الخاص بـTypeScript فلا يملك مثل هذا الكود، لأنه لا ينفّذ المراجعة التي تعرّفه.

تقول الصفحة التي تدرج الاثنين في Tier 1 أيضًا «Each SDK provides the same functionality».1 في التاريخ أدناه، وبالنسبة للمراجعة الحالية، هذه الجملة طموحية. افحص LATEST_PROTOCOL_VERSION في SDK الذي توشك على تثبيته؛ إنه سطر واحد، والادعاء الوحيد في هذا الفصل الذي سيظل مهمًا بعد عام.

انقل خادمًا خارج حاسوبك المحمول وسيظهر عميل غريب ومعه token. هذا هو النصف الذي تركه الفصل 26 جانبًا والنصف الذي لا يستطيع منتج متعدد المستخدمين تخطيه.

تضع المواصفة خادم MCP في دور OAuth 2.1 وتسميه: خادم MCP المحمي هو resource server، والعميل هو OAuth client، وauthorization server مشكلة شخص آخر.4 ومن ذلك الدور، أربع فقرات إلزامية، مقتبسة كاملة لأن إعادة صياغتها هي الطريق إلى الخطأ:

MCP servers, acting in their role as an OAuth 2.1 resource server, MUST validate access tokens as described in OAuth 2.1 Section 5.2. MCP servers MUST validate that access tokens were issued specifically for them as the intended audience, according to RFC 8707 Section 2. […] MCP clients MUST NOT send tokens to the MCP server other than ones issued by the MCP server's authorization server. MCP servers MUST only accept tokens that are valid for use with their own resources. MCP servers MUST NOT accept or transit any other tokens.4

«Must not accept or transit» هي قاعدة منع passthrough، وهي سبب وجود جهاز audience كله. الخادم الذي يعيد تشغيل bearer token الذي تسلمه عند API طرف ثالث هو confused deputy: يعير ثقته لمن ناداه. القاعدة تحظر إعادة الاستخدام، لا التخزين فقط.

جعل ذلك قابلًا للإنفاذ يحتاج أربعة RFCs، لكل واحد وظيفة.6 RFC 9728 هو كيف يجد العميل authorization server أصلًا: يخدم خادم MCP مستند protected-resource-metadata ويشير إليه 401. RFC 8707 هو parameter ‏resource — يجب على العميل إرسال URI القانونية للخادم في كل من طلب التفويض وطلب token، «سواء كانت authorization servers تدعمه أم لا»، حتى يسمّي token الصادر جمهوره. RFC 9207 يغلق الحلقة من الجهة الأخرى: يسجل العميل issuer قبل redirect ويقارن iss العائد كسلسلة مطابقة تمامًا، بلا normalisation — لا case folding، ولا حذف default-port، ولا trailing slash. وRFC 7591، Dynamic Client Registration، أصبح الآن deprecated لصالح Client ID Metadata Documents، «محفوظًا للتوافق الخلفي مع authorization servers التي لا تدعمها».4

وصّل ذلك على كلا الخادمين باستخدام token verifier لا يفعل شيئًا سوى فحص audience. سلّم TypeScript:

POST /mcp — TypeScript, with requireBearerAuthTEXT
no token            401  WWW-Authenticate: Bearer error="invalid_token",
                         error_description="Missing Authorization header",
                         scope="incidents:read",
                         resource_metadata="…/.well-known/oauth-protected-resource/mcp"
aud=other server    401  error_description="token audience is not this server"
no exp claim        401  error_description="Token has no expiration time"
right aud, no scope 403  error="insufficient_scope", scope="incidents:read"
right aud + scope   200  {"result":{"tools":[…]}}
GET /.well-known/oauth-protected-resource/mcpTEXT
{"resource":"http://127.0.0.1:8931/mcp",
 "authorization_servers":["https://auth.example.com/"],
 "scopes_supported":["incidents:read","incidents:write"],
 "resource_name":"Incidents"}

يخدم كلا SDKين ذلك المستند ويشير كلاهما بـ401 إليه، وهذه هي قصة discovery كاملة: عميل لم ير خادمك قط يتعلم أين يتوثق من رفض. أما 403 فهو كائن مختلف — token سليم، لكن scope ليست كذلك — ويسمي challenge ما ينقص حتى يستطيع العميل الترقّي بدل البدء من جديد.

يختلف درجتان، ولا يوجد أي من الاختلافين في المواصفة. يرفض SDK الخاص بـTypeScript token بلا expiry claim؛ أما Python فيعيد 200، لأن expires_at اختياري على AccessToken لديه وNone تعني «لا رأي». كما يحمل 403 في Python ‏error_description="Required scope: incidents:read" بلا parameter ‏scope الذي تقول المواصفة إن على الخوادم تضمينه. لا مكان في verifier لقبول default مكتبة: فحص audience عليك كتابته في أي لغة، وكذلك expiry.

ملاحظة صغيرة صادقة من التشغيل نفسه. أجاب GET على endpoint بـ404 في توصيل Express وبـ400 Bad Request: Missing session ID في Python، حيث تطلب المواصفة 405 Method Not Allowed وحيث إن «session ID» مفردات أزالتها هذه المراجعة. لا يشكل أي منهما خطرًا؛ كلاهما شكل منظومة في منتصف الهجرة.

آخر قطعة في الشحن هي أين تنشر، ولها إجابة برقم. جرى الزحف اليوم إلى كل خادم في السجل الرسمي عند أحدث إصدار له:7

الخوادم
الإجمالي، أحدث إصدار وغير محذوف28,170
نشط / deprecated27,853 / 317
يشحن حزمة واحدة قابلة للتثبيت على الأقل13,065
remote فقط — URL ولا شيء يُثبّت14,696
npm8,275
PyPI3,603
OCI images867
حزم mcpb706
NuGet / Cargo107 / 43

قراءتان في اتجاهين متعاكسين. بحسب الخوادم المنشورة، يتقدم npm بنسبة 2.3 إلى 1 — الرقم الذي يقتبسه الناس عندما يقولون إن المنظومة TypeScript. وبحسب التنزيلات، تتقدم Python: خلال آخر ثلاثين يومًا أخذ mcp ‏286.7 مليونًا مقابل @modelcontextprotocol/sdk عند 194.7 مليونًا، قبل إضافة fastmcp عند 72.1 مليونًا.7 كلاهما Tier 1، والـschema المعيارية هي schema.ts، والدليل الرسمي «Build an MCP server» يفتح على تبويب Python.1 أي نصف من ذلك كان في ذهنك، فالنصف الآخر صحيح أيضًا.

والصف الأهم من كليهما: أكثر من نصف السجل — 14,696 من 28,170 — لا يملك شيئًا يُثبّت. تلك خدمات ويب. وتتفق إحصاءات transport من الجهة الأخرى: من بين 14,290 إدخال حزمة، يعلن 13,787 عن stdio؛ ومن بين 16,640 إدخال remote، يعلن 15,570 عن Streamable HTTP و1,070 ما زالت تعلن HTTP+SSE deprecated. لذلك فعبارة «خادم MCP هو subprocess على حاسوبك المحمول» تصف أقلية آخذة في الانكماش، وكل واحد من الـ14,696 يحتاج القسم أعلاه لا environment variable.

عرض التفاصيل

ثنائي اللغة عمدًا، والسابقة لذلك.

هذا هو الفصل الوحيد ثنائي اللغة في الدورة، لأن الجواب الصادق ينقسم: السجل npm-first والتنزيلات Python-first، في الوقت نفسه، اليوم. كتابة واحدة من اللغتين ستتنازل عن نصف السؤال وتسيء وصف المنظومة أثناء ذلك. هناك سابقة علنية — تدرج Hugging Face MCP Course ضمن متطلباتها «Experience with at least one programming language (Python or TypeScript examples will be shown)»، وتدرّس الاثنين.8 البروتوكول الذي تتمثل قيمته كلها في عدد implementations مكان سيئ لأن يكون أحادي اللغة.

قسم مؤرخ: كل ما سبق له مدة صلاحية

رابط إلى القسم: قسم مؤرخ: كل ما سبق له مدة صلاحية

قُرئ وقيس في 7 سبتمبر 2026، مقابل مراجعة البروتوكول 2026-07-28.

القيمة
@modelcontextprotocol/sdk1.30.0، نُشر في 27 يوليو 2026؛ 4,322,438 بايت بعد الفك، 693 ملفًا، 17 dependency مباشرة
أحدث مراجعة ينفذها2025-11-25
mcp (PyPI)2.1.1، نُشر في 25 أغسطس 2026؛ wheel بحجم 357,912 بايت، إضافة إلى mcp-types 2.1.1 عند 69,656 بايت
أحدث مراجعة ينفذها2026-07-28
طبقات SDKTypeScript وPython وC# وGo وRust في Tier 1؛ Java وRuby في Tier 2؛ Swift وPHP وKotlin في Tier 3
خوادم السجل28,170
التنزيلات، آخر 30 يومًاmcp 286,653,871 · fastmcp 72,097,269 · @modelcontextprotocol/sdk 194,679,333

ملاحظة هجرة واحدة ليست رقمًا. في mcp 2.x، أُعيدت تسمية FastMCP إلى MCPServer، وما زال معظم الدروس على الإنترنت تقريبًا يفتح بالاستيراد القديم. يشحن SDK module هدفها الوحيد شرح ذلك، وهو ألطف deprecation في هذا الفصل:

from mcp.server.fastmcp import FastMCPTEXT
ModuleNotFoundError: No module named 'mcp.server.fastmcp'. This is mcp 2.x,
where FastMCP was renamed to MCPServer (from mcp.server.mcpserver import
MCPServer) and other APIs changed; see the migration guide … or pin 'mcp<2'
to keep running v1 code.

مع الجدول أمامك، التوصية مملة، وهذه علامة جيدة.

إذا كان الخادم يعيش داخل تطبيق ويب تشغّله بالفعل، فاكتبه بـTypeScript. العملية نفسها، والـdeploy نفسه، وrequest handler نفسه؛ Streamable HTTP هو endpoint تضيفه بجانب الآخرين؛ و13.9 MiB و145 ms مجانيان لأن runtime كان يعمل أصلًا. هذا هو حال معظم الخوادم remote البالغ عددها 14,696.

إذا كان الخادم يغلف أدوات بيانات، فاكتبه بـPython. ما تكشفه هو pandas، وعميل warehouse، وتحويلات بحجم notebook، والخادم بلغة أخرى سيكون subprocess call يرتدي schema. سبعمئة ميلي ثانية من import في خدمة تبدأ مرة واحدة ليست كلفة؛ في subprocess يعيد المضيف تشغيله طوال اليوم، هي كلفة.

وفي الوقت الحالي، يتفوق صف المراجعة على الاثنين. إذا احتجت 2026-07-28 — طلبات متعددة الرحلات، وresultType، وcache hints، وserver/discover — فأحد SDKين يملكها اليوم والآخر لا.

يمكنك الآن شحن الخادم نفسه بأي من اللغتين، والدفاع عن الاختيار بجدول بدل تفضيل، وتشغيله فوق كلا وسيلتي النقل الحيتين، وتسليمه token سيرفضه.

ما بنيته ما زال function: ‏schema، وendpoint، وشيء deterministic يستدعيه model. هناك فئة كاملة من المعرفة لا تلائم هذا الشكل — كيف نكتب نحن postmortem، وما الحقول التي تحتاجها تقارير الحوادث لدينا، والترتيب الذي نفعل به الأشياء ولماذا. إنها procedure، وهي prose، وإجبارها على دخول وصف أداة هو الطريقة التي تنمو بها system prompts إلى ألفي token مدفوعة في كل turn مفرد سواء كانت المحادثة عن الحوادث أم لا.

الفصل 28 هو الجواب الآخر: مجلد فيه SKILL.md يقرأه model بدلًا من استدعائه، محمّلًا على ثلاثة مستويات بحيث لا تكلف المادة المرجعية شيئًا تقريبًا حتى turn الذي تُحتاج فيه. لا يملك لغة رئيسية، وهذا أول ما يعلّمه.


قيس كل ما هنا في 7 سبتمبر 2026، على Node 22.22.3 وPython 3.14.4، مقابل @modelcontextprotocol/sdk 1.30.0 مع zod 3.25.76 وmcp 2.1.1، وكل منها مثبت في دليل مؤقت مستقل. التوقيتات هي وسائط 25 تشغيلًا، wall clock من spawn إلى السطر الحامل لرد tools/list؛ وعدد tokens هو o200k_base عبر tiktoken على JSON لكل تعريف. لم تُستدعَ أي API مدفوعة: لا يحتاج أي شيء هنا إلى model.

الخادمان يبلغان 81 و63 سطرًا غير فارغ؛ أُعيد إنتاج واحدة من أدواتهما الثلاث أعلاه باللغتين، ولا تختلف التسجيلات الأربعة الأخرى إلا كما وُصف. سياسة كشف الأخطاء في SDK الخاص بـPython مقتبسة من docstrings الخاصة بـToolError وUnexpectedToolError في mcp/server/mcpserver/exceptions.py؛ وdefault الخاص بـpretty-printing هو pydantic_core.to_json(result, fallback=str, indent=2) في mcp/server/mcpserver/resources/types.py وutilities/func_metadata.py. ثوابت protocol-version هي LATEST_PROTOCOL_VERSION في mcp_types/version.py وفي types.js الخاصة بـSDK ‏TypeScript، وكلاهما قُرئ من الحزم المثبتة لا من changelog.

  1. SDKs، modelcontextprotocol.io/docs/sdk، وBuild an MCP server، modelcontextprotocol.io/docs/develop/build-server، وكلاهما قُرئ في 7 سبتمبر 2026. مصدر جدول tiers، وجملة «Each SDK provides the same functionality but follows the idioms and best practices of its language»، وترتيب تبويبات اللغة في الدليل التعليمي: Python وTypeScript وJava وKotlin وC# وRuby وRust وGo، وقاعدة logging المقتبسة عن print() وstdout. 2 3 4

  2. stdio transport، .../basic/transports/stdio. مصدر framing بأسطر جديدة وقاعدة نقاء stdout. يقرأ الفصل 26 هذه الصفحة كاملة؛ وتُذكر هنا للسطر الذي يخالفه الخادم المكسور.

  3. MCP Inspector، modelcontextprotocol.io/docs/2026-07-28/tools/inspector، قُرئ في 7 سبتمبر 2026. حزمة واحدة، وثلاثة عملاء خلف binary واحد — web و--cli و--tui — تشترك في core واحد، ومجموعة transports واحدة، وحالة OAuth واحدة على القرص. أنتج CLI آثار الفهرس هنا.

  4. Authorization، modelcontextprotocol.io/specification/2026-07-28/basic/authorization، قُرئ في 7 سبتمبر 2026. مصدر دور resource-server؛ والفقرات الأربع المتعلقة بالتعامل مع token والمقتبسة كاملة؛ واشتراط أن تنفذ الخوادم RFC 9728 وأن يستخدمه العملاء للاكتشاف؛ وقواعد parameter ‏resource وتعريف canonical-URI؛ وجدول issuer-validation؛ وdeprecation الخاص بـDynamic Client Registration؛ وجدول 401/403/400 وchallenge ‏insufficient_scope؛ واستثناء stdio: «Implementations using an STDIO transport SHOULD NOT follow this specification, and instead retrieve credentials from the environment.» 2 3 4

  5. Streamable HTTP، .../basic/transports/streamable-http، وTransports overview، .../basic/transports. مصدر قاعدة POST ذات endpoint واحد، ومتطلب Accept المزدوج، وheader ‏MCP-Protocol-Version وقاعدته التي تشترط مطابقة الجسم، وheaders ‏Mcp-Method وMcp-Name الموصوفة بأنها «REQUIRED for compliance»، وإزالة GET stream وsessions وLast-Event-ID، وإرشاد 405، والتحقق الإلزامي من Origin، وتصنيف transport ‏HTTP+SSE بتاريخ 2024-11-05 على أنه Deprecated ضمن SEP-2596. 2 3 4

  6. الأربعة التي تستند إليها المواصفة، مع المسودة التي تفصلها: The OAuth 2.1 Authorization Framework، draft-ietf-oauth-v2-1-13. Campbell, B., Bradley, J. and Tschofenig, H., Resource Indicators for OAuth 2.0, RFC 8707, February 2020 — parameter ‏resource وaudience الذي يربطه. Jones, M.B., Hunt, P. and Parecki, A., OAuth 2.0 Protected Resource Metadata, RFC 9728, April 2025 — المستند الذي يشير إليه 401. Meyer zu Selhausen, K. and Fett, D., OAuth 2.0 Authorization Server Issuer Identification, RFC 9207, March 2022 — parameter ‏iss والمقارنة كسلسلة مطابقة تمامًا. Richer, J. (ed.) et al., OAuth 2.0 Dynamic Client Registration Protocol, RFC 7591, July 2015، deprecated لهذا الاستخدام. وJones, M. and Hardt, D., The OAuth 2.0 Authorization Framework: Bearer Token Usage, RFC 6750, October 2012، القسم 3، لشكل challenge ‏WWW-Authenticate أعلاه.

  7. سجل MCP الرسمي، registry.modelcontextprotocol.io/v0/servers، جرى الزحف إليه في 7 سبتمبر 2026 باستخدام version=latest: ‏282 صفحة، و28,170 خادمًا، جُمعت بإحصاء registryType على أسماء خوادم مميزة. أرقام التنزيل: api.npmjs.org/downloads/point/last-month لـ@modelcontextprotocol/sdk (194,679,333 للفترة 8 أغسطس – 6 سبتمبر 2026) وpypistats.org/api/packages/<name>/recent لـmcp وfastmcp، وكلها قُرئت في اليوم نفسه. أحجام الحزم من مستند npm registry وPyPI JSON API. 2

  8. MCP Course، Hugging Face، huggingface.co/learn/mcp-course، الوحدة 0، قُرئ في 7 سبتمبر 2026: ضمن المتطلبات، «Experience with at least one programming language (Python or TypeScript examples will be shown)».

هل أنت مستعد لتترك الاختيار لـ LIA؟

ابنِ بكل نماذج الذكاء الاصطناعي في مكان واحد — ابدأ مجانًا اليوم.