انتشار یک MCP Server: TypeScript و Python، با اندازهگیری واقعی
یک server یکسان، دو بار نوشته شد: سه tool، یک resource، یک prompt. 94 package در برابر 28 و cold start 145 ms در برابر 709.
در این صفحه
این کل بحث زبان است، اندازهگیریشده، پیش از آنکه حتی یک کلمه دربارهاش گفته شود.
node ./incidents.js 144.5 ms
python incidents.py 709.4 ms
npx incidents-mcp 712.6 msدو خط اول همان مقایسهای است که همه میخواهند. خط سوم همان TypeScript server خط اول است، اما به همان شکلی اجرا شده که واقعاً توزیع میشود — و فقط سه میلیثانیه با Python فاصله دارد.
فصل 26 Model Context Protocol را با مشخصات خودش و با JSON-RPC خام خواند، چون JSON-RPC خام زبان ندارد. این فصل دو زبان دارد، و وزن استدلال همینجاست: همان server، دو بار نوشتهشده. سه tool، یک resource، یک prompt، هر دو SDK، بدون میانبر در هیچ سمت. بعد transportها، inspector، خطای 401، و عددهایی که هیچکس منتشر نکرده است.
Server، و اینکه چرا این پنج چیز را در خود دارد
لینک به بخش: Server، و اینکه چرا این پنج چیز را در خود داردیک incident log. سه tool، چون جداسازی خواندن و نوشتن در فصل 18 باید قابل دیدن باشد: search_incidents میخواند، open_incident مینویسد و یک handle برمیگرداند، resolve_incident آن handle را میگیرد و میبندد. یک resource، incidents://open، چون خواندن فهرست فعلی چیزی است که application وصل میکند. یک prompt، postmortem، چون «این را بنویس» slash command یک انسان است. این همان سلسلهمراتب کنترل فصل 26 است — model، application، انسان — که به پنج ثبت تبدیل شده.
اهمیت handle بیشتر از چیزی است که به نظر میرسد. فصل 26 یک تقویم اسباببازی را با نگهداشتن state در یک آرایه در سطح module خراب کرد: protocol هیچ session ندارد، پس یک creation tool یک شناسه opaque برمیگرداند و هر فراخوانی بعدی آن را مثل یک argument معمولی میگیرد. هیچچیز در هیچکدام از دو file فرض نمیکند caller همان processی است که آن را باز کرده.
اینجا همان tool را در هر دو زبان، کنار هم ثبتشده، میبینید:
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 }) }] };
},
);@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.")اول چیزی را بخوانید که یکسان است، چون یافته همین است. هر دو یک name، یک description، دو string argument توصیفشده و سه annotation تعریف میکنند؛ هر دو یک function هستند؛ هیچکدام JSON-RPC، framing، stdout یا نسخه protocol را ذکر نمیکند. دو SDK به یک شکل یکسان همگرا شدهاند، و «Tier 1» هم باید همین معنا را بدهد.1
دو تفاوت واقعیاند و هر دو بعداً برمیگردند. TypeScript argumentها را با یک schema library توصیف میکند — اینجا Zod — و schema مقداری است که شما مینویسید. Python آنها را با type hintهای خود function توصیف میکند و هنگام import میخواند؛ برای همین چیزهایی درباره function میداند که file TypeScript هرگز به او نگفته. و مسیر error: TypeScript یک tool result با isError برمیگرداند، Python exception پرتاب میکند. این را نگه دارید.
چهار ثبت دیگر از نظر ساختاری هیچ تفاوتی ندارند. Resource برابر است با server.registerResource("open-incidents", "incidents://open", …) در برابر @server.resource("incidents://open", …)؛ prompt برابر است با registerPrompt در برابر @server.prompt. خط آخر هر file transport است: await server.connect(new StdioServerTransport()) در برابر server.run().
کل fileها: 81 خط غیرخالی و 3,060 byte از TypeScript در برابر 63 و 2,555. با همان احتیاطی که لازم است بخوانید — line count به اندازه زبان، formatter را هم اندازه میگیرد؛ به همین دلیل هیچکدام از این عددها در جدول headline پایین نیامدهاند.
یک client، هر دو server
لینک به بخش: یک client، هر دو serverاثبات نامرئیبودن زبان، یک client است که دو بار و در یازده خط اجرا میشود:
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));آن را به نوبت به هر server بدهید. خروجی واقعی، کوتاهشده:
$ 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}"}]همان toolها، همان ترتیب، همان handle. یک TypeScript client نمیتواند بفهمد server با چه زبانی نوشته شده، و هرگز هم نمیپرسد. این کل وعده یک protocol است، و پابرجا میماند.
حالا به whitespace در نتیجه دوم نگاه کنید، چون تزئینی نیست: Python SDK payloadها را با pydantic_core.to_json(result, fallback=str, indent=2) serialise میکند. در resource read با دو incident در فهرست، body در TypeScript برابر 136 character و 37 o200k_base token است؛ body در Python برابر 185 و 62 است. شصتوهشت درصد token بیشتر برای rowهای یکسان، هر بار به هزینه هر کسی که resource را داخل prompt میخواند.
Catalogue نیز همان داستان را با علت بزرگتری دارد. هر دو server، همان سه tool، tools/list کلیدبهکلید وزن شد:
| کلید | TypeScript | Python |
|---|---|---|
name | 21 | 21 |
description | 46 | 46 |
annotations | 46 | 46 |
inputSchema | 211 | 192 |
outputSchema | — | 187 |
execution | 27 | — |
| مجموع | 342 | 480 |
Schemaهای input در Python ارزانترند — bridge مربوط به Zod در TypeScript روی هرکدام یک $schema و یک additionalProperties میزند. کل فاصله 138-token یک output schema است که هیچکس ننوشته. resolve_incident با -> Incident annotation شده، پس SDK یک JSON Schema برای return type استخراج کرده و فرستاده. واقعاً مفید است — همان چیزی است که به client اجازه میدهد structuredContent را validate کند — و 187 token از context window شماست که بهخاطر یک type hint وارد میشود. قاعده فصل 24 درباره اینکه definitionها جای material مهم را میگیرند، درباره schemaهایی هم صدق میکند که نمیدانستید دارید.
عمداً خرابش کنید: error messageی که نشت کرد
لینک به بخش: عمداً خرابش کنید: error messageی که نشت کرددو مسیر error بالا انتخاب سبک نیستند. به هر server یک tool بدهید که همانطور fail شود که یک integration واقعی fail میشود، و ببینید چه چیزی به model میرسد.
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}TypeScript SDK یک آدرس داخلی، یک port، یک database name و یک service account را داخل context مدل گذاشت. Python SDK هیچکدام را آنجا نگذاشت؛ traceback به stderr رفت و روی server ماند.
هیچکدام bug نیست. هر دو تصمیماند، و تصمیم Python در docstring خودش نوشته شده: یک ToolError «شکستی است که انتظارش را داشتید» و message آن «در content برای خواندن model» برگردانده میشود؛ هر چیز دیگر «مثل crash تلقی میشود: model فقط Error executing tool <name> را میبیند، و server traceback را در ERROR log میکند». کلاس مربوط به حالت crash بقیه را بلند میگوید — «هیچچیز از original به client نمیرسد».
هر دو رفتار نیمی از مواقع غلطاند. فصل 18 استدلال کرد که validation error باید بهصورت tool result برگردد تا model بتواند بخواند و اصلاح کند، چون در بیشتر integrationها همان خط بیشترین leverage را دارد؛ در سمت Python این مستلزم آن است که ToolError را صراحتاً raise کنید، و یک ValueError ساده جمله مفید را دور میاندازد. استدلال فصل 30 در جهت مخالف میرود: هر چیزی که یک tool برمیگرداند وارد contextی میشود که prompt injection بعدی میتواند تلاش کند دوباره آن را بیرون بکشد، و متن exception بازبینینشده کمممیزیترین متن در سیستم شماست.
قاعدهای که از هر دو جان سالم به در میبرد: برای هر tool تصمیم بگیرید یک failure مجاز است چه چیزی بگوید، و آن string را خودتان بنویسید. هرگز اجازه ندهید متن پیشفرض یک exception در هیچکدام از دو زبان تصمیم بگیرد.
عمداً خرابش کنید: یک خط روی standard output
لینک به بخش: عمداً خرابش کنید: یک خط روی standard outputTutorial رسمی قاعده را بیابهام میگوید: «برای serverهای مبتنی بر STDIO: هرگز روی stdout ننویسید. نوشتن روی stdout پیامهای JSON-RPC را corrupt میکند و server شما را میشکند. function print() بهطور پیشفرض روی stdout مینویسد، پس آن را کاملاً از یک STDIO server بیرون نگه دارید.»1 فصل 26 نسخه normative را نقل کرد — یک server «MUST NOT write anything to its stdout that is not a valid MCP message».2
به هر server یک خط اضافه کنید و raw stream را بخوانید:
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 نیست. Processی که stdout آن pipe باشد نه terminal، streamی block-buffered میگیرد؛ بنابراین خط اضافی هر وقت buffer تصمیم بگیرد flush میشود — اینجا، هنگام exit، بعد از responseی که قبل از آن نوشته شده بود. Corruption جایی ظاهر نمیشود که bug هست. flush=True را اضافه کنید، یا libraryای که flush میکند، و جابهجا میشود.
بعد بخشی که توضیح میدهد چرا این وارد production میشود. Server خراب را به سه client بدهید:
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 warningParser هفتخطی فوراً میمیرد. Client رسمی و Inspector هر دو شانه بالا میاندازند — خط را skip میکنند و ادامه میدهند. قاعدهای که فقط clientهایی را میشکند که هیچکس استفاده نمیکند، قاعدهای است که سالم به production میرسد؛ برای همین ارزش دارد اینجا عمداً آن را خراب کنیم، نه در log مشتری.
حالت CLI در Inspector همان نیمهای است که فراموش میشود: npx @modelcontextprotocol/inspector --cli <command> --method tools/list یک catalogue چاپ میکند و خارج میشود، و همین آن را به شکلی scriptable میکند که browser UI نیست.3
هر دو SDK تمیز، در directoryهای جداگانه خودشان و بدون چیز shared نصب شدند:
| TypeScript | Python | |
|---|---|---|
| package | @modelcontextprotocol/sdk 1.30.0 + zod 3.25.76 | mcp 2.1.1 |
| آخرین protocol revision پیادهسازیشده | 2025-11-25 | 2026-07-28 |
| transitive packageهای نصبشده | 94 | 28 |
| اندازه نصبشده | 13.9 MiB | 44.3 MiB |
| fileهای روی disk | 3,386 | 2,018 |
| third-party packageهای loadشده برای serve کردن stdio | 8 از 94 | 18 از 28 |
| شروع bare interpreter، median | 19.4 ms | 11.1 ms |
spawn → پاسخ tools/list، median از 25 | 144.5 ms | 709.4 ms |
catalogue tools/list، tokenهای o200k_base | 342 | 480 |
هر row در جهت متفاوتی غافلگیر میکند، و به همین دلیل ارزش دارد مقایسه را اجرا کنید نه اینکه فرض بگیرید.
TypeScript بیش از سه برابر package نصب میکند و کمتر از یکسوم byte. 94 dependency یعنی اکوسیستم npm همان خودش است — fast-deep-equal، es-errors، dunder-proto. 28 تای Python کمترند و عظیم: cryptography، pydantic-core و uvicorn artefactهای compiled هستند. اگر غریزه شما این است که باید نگران تعداد dependency باشید، این row نمونه نقض است.
Interpreter در Python سریعتر از Node شروع میشود، و فاصله کم نیست — 11.1 ms در برابر 19.4 ms روی یک برنامه خالی. پس 565 ms در row مربوط به cold-start زبان نیست. SDK است، و row packageهای loadشده میگوید چرا:
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, …Serverی که تنها I/O آن یک pipe است، پیش از خواندن اولین خط خود یک ASGI web server، یک HTTP client و یک TLS library import میکند. TypeScript SDK هم Express، Hono، jose و eventsource را ship میکند — اما روی disk خواندهنشده میمانند، چون مرز package آنها را از import مربوط به server/stdio.js بیرون نگه میدارد. Package در Python یک import graph است، پس import mcp همه آن است: python -X importtime مقدار 727 ms را به import mcp.server.mcpserver نسبت میدهد — عددی که زیر import profiler اندازهگیری شده، و برای همین بالاتر از 709 msی درمیآید که اجرای بدون profiler از spawn تا answer طول میکشد — و 269 ms از آنها را فقط به subtree مربوط به mcp.types نسبت میدهد — wire typeها Pydantic model هستند، یک class برای هر protocol message در هر revision، و ساختنشان کاری است که هنگام import انجام میشود. این یک design trade است، نه شلختگی — importهای eager همان دلیلیاند که Python SDK میتواند در خط بعد run(transport="streamable-http") را بدون install دوم به شما بدهد.
و بعد آخرین row از block آغازین کل استدلال را برمیگرداند. TypeScript server را درست package کنید — یک entry bin، یک shebang، npm link، بدون چیزی برای download — و آن را از طریق npx با --no-install اجرا کنید، یعنی همانطور که یک stdio server منتشرشده واقعاً start میشود:
node ./incidents.js 144.5 ms
npx incidents-mcp 712.6 ms (+568.1 ms of launcher)
python incidents.py 709.4 msLauncher برای هر start برابر 568 ms هزینه دارد — چهارونیم برابر کل import مربوط به TypeScript SDK — و در هر launch پرداخت میشود، چون یک MCP host یک stdio server را با اجرای همان command start میکند. پس شکل صادقانه جمله «TypeScript پنج برابر سریعتر start میشود» این است: بله، تا وقتی آن را به روش معمول توزیع نکنید. احتمالاً همین caveat برای uvx هم صدق میکند؛ روی این machine هیچ uv نصب نبود، پس چنین rowای وجود ندارد. هیچ چیز اندازهگیرینشدهای وارد جدول نمیشود.
دو transport، و فقط دو تا
لینک به بخش: دو transport، و فقط دو تافصل 26 framing مربوط به stdio را پوشش داد. دو چیز را برای اینجا گذاشت.
اول: اجرای یک server با npx یا uvx همان stdio transport است. هیچ «package mode» جداگانهای وجود ندارد. Configuration یک host یک command و argumentها را نام میبرد؛ host آن را spawn میکند و از طریق pipeها حرف میزند. برای همین «این را چطور توزیع کنم» و «کدام transport را صحبت میکند» در حالت local یک سؤالاند، و به همین دلیل هزینه launcher به فصلی درباره shipping تعلق دارد.
دوم: stdio اصلاً بخش authorization ندارد، و specification این را در یک خط میگوید — implementationهایی که از stdio استفاده میکنند «SHOULD NOT follow this specification, and instead retrieve credentials from the environment».4 مدل security آن مدل operating system است، و محدودیتش هم همین: یک local subprocess دقیقاً به یک machine و یک user سرویس میدهد.
Transport زنده دیگر Streamable HTTP است: یک endpoint واحد که POST میپذیرد، یک HTTP request برای هر JSON-RPC message، و header Accept که باید هر دو application/json و text/event-stream را list کند، چون server برای هر request انتخاب میکند با کدامیک پاسخ دهد.5 فصل 14 آن event stream را دستی parse کرد، پس هیچچیز در wire format جدید نیست — فقط چیزی که دور آن پیچیده. سه الزام revision فعلی بهراحتی از قلم میافتند و هر سه قابل تستاند:
Version header باید با body همخوان باشد
لینک به بخش: Version header باید با body همخوان باشدهر POST دارای MCP-Protocol-Version است، و مقدار آن باید با protocolVersion داخل _meta خود request match کند. Mismatch یک 400 با header-mismatch error است، نه شانه بالا انداختن.5
دو header دیگر برای compliance لازماند
لینک به بخش: دو header دیگر برای compliance لازماندMcp-Method روی هر request روش را mirror میکند؛ Mcp-Name روی tools/call، resources/read و prompts/get، params.name یا params.uri را mirror میکند. وجود دارند تا proxy بتواند بدون parse کردن bodyها route کند.5
شکلهای قدیمی حذف شدهاند، و با امتناع پاسخ میدهند
لینک به بخش: شکلهای قدیمی حذف شدهاند، و با امتناع پاسخ میدهندGET stream، Mcp-Session-Id و resumption مربوط به Last-Event-ID همگی حذف شدند. Serverی که فقط این revision را صحبت میکند باید به GET یا DELETE با 405 Method Not Allowed پاسخ دهد، session header را بدون mint کردن session نادیده بگیرد، و Last-Event-ID را ignore کند.5
حالا اندازهگیریای که کل فصل را از نو قاببندی میکند. یک current-revision request را از طریق HTTP به هر server بفرستید.
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)"}}Constantها با رفتار همخواناند: LATEST_PROTOCOL_VERSION در Python SDK برابر 2026-07-28 است، در TypeScript SDK برابر 2025-11-25. Header-mismatch request مرحله بالا را بفرستید و Python server با 400، error -32020 و message «mcp-protocol-version header does not match the request envelope's protocol version» پاسخ میدهد؛ TypeScript SDK چنین codeای ندارد، چون revisionی را که آن را تعریف میکند پیاده نکرده.
صفحهای که هر دو را در Tier 1 list میکند همچنین میگوید «Each SDK provides the same functionality».1 در تاریخ پایین، برای revision فعلی، آن جمله آرزومندانه است. در SDKی که میخواهید install کنید LATEST_PROTOCOL_VERSION را check کنید؛ یک خط است، و تنها ادعای این فصل است که یک سال دیگر هم هنوز مهم خواهد بود.
خطای 401، و جملهای که باید نقل کرد
لینک به بخش: خطای 401، و جملهای که باید نقل کردServer را از laptop خود بیرون ببرید و client یک غریبه با token از راه میرسد. این همان نیمهای است که فصل 26 دستنخورده گذاشت و نیمهای که یک product چندکاربره نمیتواند skip کند.
Specification، MCP server را در یک نقش OAuth 2.1 قرار میدهد و نامش را هم میگوید: یک MCP server محافظتشده resource server است، client یک OAuth client است، و authorization server مشکل شخص دیگری است.4 از دل آن نقش، چهار بند اجباری، کامل نقل شدهاند چون paraphrase کردنشان همانجایی است که اشتباه رخ میدهد:
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 است، و به همین دلیل کل apparatus مربوط به audience وجود دارد. Serverی که bearer tokenی را که به او داده شده در یک third-party API replay میکند، یک confused deputy است: اعتماد خودش را به هر کسی که او را صدا زده قرض میدهد. قاعده reuse را ممنوع میکند، نه فقط storage را.
قابل اجرا کردن این قاعده چهار RFC میخواهد، هرکدام با یک کار.6 RFC 9728 راهی است که client اصلاً authorization server را پیدا میکند: MCP server یک protected-resource-metadata document serve میکند و یک 401 به آن اشاره میکند. RFC 8707 همان parameter resource است — client باید URI canonical server را در هر دو authorization request و token request بفرستد، «regardless of whether authorization servers support it»، تا token صادرشده audience خودش را نام ببرد. RFC 9207 حلقه را از سمت دیگر میبندد: client پیش از redirect، issuer را record میکند و iss برگشتی را با exact string compare میکند، بدون normalisation — نه case folding، نه حذف default-port، نه trailing slash. و RFC 7591، Dynamic Client Registration، اکنون به نفع Client ID Metadata Documents deprecated شده، «retained for backwards compatibility with authorization servers that do not support» آنها.4
این را روی هر دو server با token verifierای wire کنید که هیچ کاری جز check کردن audience انجام نمیدهد. Ladder در TypeScript:
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":[…]}}{"resource":"http://127.0.0.1:8931/mcp",
"authorization_servers":["https://auth.example.com/"],
"scopes_supported":["incidents:read","incidents:write"],
"resource_name":"Incidents"}هر دو SDK آن document را serve میکنند و هر دو یک 401 را به آن point میکنند، که کل داستان discovery همین است: clientی که هرگز server شما را ندیده، از یک refusal یاد میگیرد کجا authenticate کند. 403 موجود دیگری است — token خوب است، scope خوب نیست — و challenge چیزی را که کم است نام میبرد تا client بتواند step up کند نه اینکه از نو شروع کند.
دو پله فرق دارند، و هیچکدام از این تفاوتها در specification نیست. TypeScript SDK tokenی را که هیچ expiry claim ندارد رد میکند؛ نسخه Python 200 برمیگرداند، چون expires_at روی AccessToken آن optional است و None یعنی «نظری ندارم». و 403 در Python، error_description="Required scope: incidents:read" را بدون parameter scope حمل میکند که specification میگوید serverها باید شامل کنند. Verifier جای پذیرش default یک library نیست: audience check را شما باید در هر زبان بنویسید، expiry را هم همینطور.
یک نکته کوچک و صادقانه از همان run. GET روی endpoint در wiring مربوط به Express با 404 پاسخ داد و در Python با 400 Bad Request: Missing session ID، جایی که specification 405 Method Not Allowed میخواهد و جایی که «session ID» واژگان revisionی است که حذف شده. هیچکدام خطرناک نیست؛ هر دو شکل اکوسیستمی هستند که وسط migration است.
Serverها واقعاً کجا زندگی میکنند
لینک به بخش: Serverها واقعاً کجا زندگی میکنندآخرین تکه shipping این است که کجا publish میکنید، و پاسخی عدددار دارد. امروز crawled شد، همه serverها در registry رسمی در آخرین version خودشان:7
| serverها | |
|---|---|
| مجموع (آخرین version، حذفنشده) | 28,170 |
| active / deprecated | 27,853 / 317 |
| حداقل یک installable package ship میکنند | 13,065 |
| فقط remote — یک URL، بدون چیزی برای install | 14,696 |
| npm | 8,275 |
| PyPI | 3,603 |
| OCI images | 867 |
bundleهای mcpb | 706 |
| NuGet / Cargo | 107 / 43 |
دو خوانش، در دو جهت مخالف. بر اساس serverهای منتشرشده، npm با نسبت 2.3 به 1 جلوست — عددی که مردم وقتی میگویند اکوسیستم TypeScript است نقل میکنند. بر اساس download، Python جلوست: در سی روز گذشته mcp به 286.7 میلیون رسید در برابر @modelcontextprotocol/sdk با 194.7 میلیون، پیش از اضافه کردن fastmcp با 72.1 میلیون.7 هر دو Tier 1 هستند، schema normative یک schema.ts است، و tutorial رسمی «Build an MCP server» با tab مربوط به Python باز میشود.1 هر نیمهای از این را در ذهن داشتید، نیمه دیگر هم درست است.
و rowای که از هر دو مهمتر است: بیش از نصف registry — 14,696 از 28,170 — چیزی برای install ندارد. آنها web service هستند. شمارش transportها از سمت دیگر هم همین را تأیید میکند: از 14,290 package entry، 13,787 مورد stdio اعلام میکنند؛ از 16,640 remote entry، 15,570 مورد Streamable HTTP اعلام میکنند و 1,070 مورد هنوز HTTP+SSE deprecated را اعلام میکنند. پس «یک MCP server یک subprocess روی laptop شماست» اقلیتی رو به کاهش را توصیف میکند، و هر یک از آن 14,696 مورد به بخش بالا نیاز دارد نه به environment variable.
نمایش جزئیات
عمداً دوزبانه، و سابقهای برای آن.
این تنها فصل دوزبانه دوره است، چون پاسخ صادقانه دو تکه میشود: registry امروز همزمان npm-first است و downloadها Python-first. نوشتن فقط یکی از این دو، نصف سؤال را واگذار میکرد و همزمان اکوسیستم را غلط توصیف میکرد. در فضای open سابقه وجود دارد — Hugging Face MCP Course در prerequisites خود میگوید «Experience with at least one programming language (Python or TypeScript examples will be shown)»، و هر دو را آموزش میدهد.8 Protocolی که کل ارزشش تعداد implementationهاست، جای بدی برای تکزبانه بودن است.
بخش تاریخدار: هر چیز بالا که عمر مفید دارد
لینک به بخش: بخش تاریخدار: هر چیز بالا که عمر مفید داردخوانده و اندازهگیریشده در 7 سپتامبر 2026، در برابر protocol revision 2026-07-28.
| مقدار | |
|---|---|
@modelcontextprotocol/sdk | 1.30.0، منتشرشده در 27 ژوئیه 2026؛ 4,322,438 byte unpacked، 693 file، 17 dependency مستقیم |
| آخرین revisionی که پیادهسازی میکند | 2025-11-25 |
mcp (PyPI) | 2.1.1، منتشرشده در 25 اوت 2026؛ wheel با 357,912 byte، بهعلاوه mcp-types 2.1.1 با 69,656 byte |
| آخرین revisionی که پیادهسازی میکند | 2026-07-28 |
| Tierهای SDK | TypeScript، Python، C#، Go، Rust در Tier 1؛ Java، Ruby در Tier 2؛ Swift، PHP، Kotlin در Tier 3 |
| serverهای registry | 28,170 |
| downloadها، 30 روز گذشته | mcp 286,653,871 · fastmcp 72,097,269 · @modelcontextprotocol/sdk 194,679,333 |
یک migration note که عدد نیست. در mcp 2.x، FastMCP به MCPServer rename شد، و تقریباً هر tutorial آنلاین هنوز با import قدیمی باز میشود. SDK moduleای ship میکند که تنها هدفش توضیح همین است، و این considerateترین deprecation در این فصل است:
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.پس کدامیک
لینک به بخش: پس کدامیکبا جدول روبهرویتان، recommendation حوصلهسربر است، که نشانه خوبی است.
اگر server داخل web applicationی زندگی میکند که همین حالا اجرا میکنید، آن را با TypeScript بنویسید. همان process، همان deploy، همان request handler؛ Streamable HTTP یک endpoint است که کنار بقیه اضافه میکنید؛ و 13.9 MiB و 145 ms رایگاناند چون runtime از قبل بالا بوده. این وضعیت بیشتر همان 14,696 remote server است.
اگر server دور data tooling پیچیده میشود، آن را با Python بنویسید. چیزی که expose میکنید pandas، یک warehouse client، transformهایی به اندازه یک notebook، و serverی در زبان دیگر است که در عمل subprocess callای با schema پوشیده است. هفتصد میلیثانیه import در serviceای که یک بار start میشود هزینه نیست؛ در subprocessی که host تمام روز relaunch میکند، هست.
و فعلاً، row مربوط به revision بر هر دو اولویت دارد. اگر به 2026-07-28 نیاز دارید — multi-round-trip requestها، resultType، cache hintها، server/discover — امروز یکی از این دو SDK آن را دارد و دیگری ندارد.
بعد از این کجا میرویم
لینک به بخش: بعد از این کجا میرویمحالا میتوانید همان server را در هرکدام از دو زبان ship کنید، انتخاب را با جدول دفاع کنید نه با ترجیح، آن را روی هر دو transport زنده اجرا کنید، و tokenی به آن بدهید که رد خواهد کرد.
چیزی که ساختید هنوز یک function است: یک schema، یک endpoint، چیزی deterministic که model invoke میکند. یک طبقه کامل از knowledge در این شکل جا نمیشود — اینکه ما postmortem را چطور مینویسیم، گزارشهای incident ما چه fieldهایی لازم دارند، ترتیب کارهای ما چیست و چرا. این procedure است، prose است، و زورچپان کردنش داخل tool description همان راهی است که system promptها را به دو هزار token میرساند؛ هزینهای که در تکتک turnها پرداخت میشود، چه conversation درباره incident باشد چه نباشد.
فصل 28 پاسخ دیگر است: folderای با یک SKILL.md داخلش که model بهجای call کردن میخواند، در سه سطح load میشود تا reference material تقریباً هیچ هزینهای نداشته باشد تا همان turnای که لازم میشود. زبان اصلی ندارد، و همین اولین چیزی است که یاد میدهد.
Sources and method
لینک به بخش: Sources and methodهمهچیز اینجا در 7 سپتامبر 2026، روی Node 22.22.3 و Python 3.14.4، در برابر @modelcontextprotocol/sdk 1.30.0 با zod 3.25.76 و mcp 2.1.1 اندازهگیری شد، هرکدام نصبشده در directory دورریختنی خودش. Timingها median از 25 launch هستند، wall clock از spawn تا lineای که response مربوط به tools/list را حمل میکرد؛ token countها o200k_base از طریق tiktoken روی JSON هر definition هستند. هیچ paid APIای call نشد: هیچچیز اینجا به model نیاز ندارد.
دو server دارای 81 و 63 خط غیرخالیاند؛ یکی از سه tool آنها بالا در هر دو زبان بازتولید شده، و چهار ثبت دیگر فقط همانطور که توضیح داده شد تفاوت دارند. Policy مربوط به error-disclosure در Python SDK از docstringهای 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 است. Constantهای protocol-version برابر LATEST_PROTOCOL_VERSION در mcp_types/version.py و در types.js مربوط به TypeScript SDK هستند، هر دو از packageهای نصبشده خوانده شدند نه از changelog.
ارجاعات
لینک به بخش: ارجاعات-
SDKs،
modelcontextprotocol.io/docs/sdk، و Build an MCP server،modelcontextprotocol.io/docs/develop/build-server، هر دو خواندهشده در 7 سپتامبر 2026. منبع جدول tier، جمله «Each SDK provides the same functionality but follows the idioms and best practices of its language»، ترتیب language-tabهای tutorial (Python، TypeScript، Java، Kotlin، C#، Ruby، Rust، Go)، و قاعده logging نقلشده دربارهprint()وstdout. ↩ ↩2 ↩3 ↩4 -
stdio transport،
.../basic/transports/stdio. منبع newline framing و قاعده purity مربوط بهstdout. فصل 26 این صفحه را کامل میخواند؛ اینجا برای خطی cite شده که server خراب آن را نقض میکند. ↩ -
MCP Inspector،
modelcontextprotocol.io/docs/2026-07-28/tools/inspector، خواندهشده در 7 سپتامبر 2026. یک package، سه client پشت یک binary — web،--cliو--tui— با یک core مشترک، یک مجموعه transport مشترک و یک OAuth state روی disk. CLI، catalogue traceهای اینجا را تولید کرد. ↩ -
Authorization،
modelcontextprotocol.io/specification/2026-07-28/basic/authorization، خواندهشده در 7 سپتامبر 2026. منبع نقش resource-server؛ چهار بند token-handling که کامل نقل شدند؛ الزام پیادهسازی RFC 9728 توسط serverها و استفاده clientها از آن برای discovery؛ قواعد parameterresourceو تعریف canonical-URI؛ جدول issuer-validation؛ deprecation مربوط به Dynamic Client Registration؛ جدول401/403/400و challenge مربوط بهinsufficient_scope؛ و exemption مربوط به stdio، «Implementations using an STDIO transport SHOULD NOT follow this specification, and instead retrieve credentials from the environment.» ↩ ↩2 ↩3 ↩4 -
Streamable HTTP،
.../basic/transports/streamable-http، و Transports overview،.../basic/transports. منبع قاعده single-endpoint POST، الزام dualAccept، headerMCP-Protocol-Versionو قاعده must-match-the-body آن، headerهایMcp-MethodوMcp-Nameکه بهعنوان «REQUIRED for compliance» توصیف شدهاند، حذف GET stream، sessionها وLast-Event-ID، guidance مربوط به405، validation اجباریOrigin، و طبقهبندی transport مربوط به 2024-11-05 HTTP+SSE بهعنوان Deprecated تحت SEP-2596. ↩ ↩2 ↩3 ↩4 -
چهار موردی که specification بر آنها تکیه دارد، همراه با draftی که profile میکند: 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، فوریه 2020 — parameterresourceو audienceی که bind میکند. Jones, M.B., Hunt, P. and Parecki, A., OAuth 2.0 Protected Resource Metadata، RFC 9728، آوریل 2025 — documentی که یک401به آن point میکند. Meyer zu Selhausen, K. and Fett, D., OAuth 2.0 Authorization Server Issuer Identification، RFC 9207، مارس 2022 — parameterissو exact-string comparison. Richer, J. (ed.) et al., OAuth 2.0 Dynamic Client Registration Protocol، RFC 7591، ژوئیه 2015، برای این use deprecated شده. و Jones, M. and Hardt, D., The OAuth 2.0 Authorization Framework: Bearer Token Usage، RFC 6750، اکتبر 2012، section 3، برای شکل challenge مربوط بهWWW-Authenticateدر بالا. ↩ -
Registry رسمی MCP،
registry.modelcontextprotocol.io/v0/servers، crawled در 7 سپتامبر 2026 باversion=latest: 282 page، 28,170 server، شمردهشده باregistryTypeروی server nameهای distinct. ارقام download:api.npmjs.org/downloads/point/last-monthبرای@modelcontextprotocol/sdk(194,679,333 برای 8 اوت تا 6 سپتامبر 2026) وpypistats.org/api/packages/<name>/recentبرایmcpوfastmcp، هر دو همان روز خوانده شدند. Package sizeها از npm registry document و PyPI JSON API میآیند. ↩ ↩2 -
MCP Course، Hugging Face،
huggingface.co/learn/mcp-course، unit 0، خواندهشده در 7 سپتامبر 2026: در میان prerequisites، «Experience with at least one programming language (Python or TypeScript examples will be shown)». ↩