MCP Server शिप करें: TypeScript और Python, माप के साथ
एक ही server दो बार—तीन tools, एक resource, एक prompt—फिर तुलना: 94 packages बनाम 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 की है, बस उस तरह launch की गई जैसे उसे सच में distribute किया जाएगा—और वह Python से तीन milliseconds दूर आकर रुकती है।
अध्याय 26 ने Model Context Protocol को उसकी अपनी specification के सामने raw JSON-RPC के साथ पढ़ा था, क्योंकि raw JSON-RPC की कोई भाषा नहीं होती। इस अध्याय में दो हैं, और तर्क का वजन यहीं गिरता है: वही server, दो बार लिखा गया। तीन tools, एक resource, एक prompt, दोनों SDKs, किसी तरफ कोई shortcut नहीं। फिर transports, inspector, 401, और वे numbers जिन्हें किसी ने publish नहीं किया।
Server, और इसमें ये पाँच चीज़ें क्यों हैं
सेक्शन का लिंक: Server, और इसमें ये पाँच चीज़ें क्यों हैंएक incident log. तीन tools, क्योंकि अध्याय 18 का reads और writes के बीच split दिखना चाहिए: search_incidents पढ़ता है, open_incident लिखता है और handle लौटाता है, resolve_incident उस handle को लेकर बंद करता है। एक resource, incidents://open, क्योंकि current list पढ़ना ऐसी चीज़ है जिसे application attach करती है। एक prompt, postmortem, क्योंकि “इसे लिख दो” किसी व्यक्ति की slash command है। यही अध्याय 26 की control hierarchy—model, application, person—पाँच registrations में बदली हुई है।
Handle दिखने से ज़्यादा मायने रखता है। अध्याय 26 ने एक toy calendar को module-level array में state रखकर तोड़ दिया था: protocol में session नहीं है, इसलिए creation tool एक opaque identifier लौटाता है और बाद की हर call उसे ordinary argument की तरह लेती है। किसी भी file में यह मानकर नहीं चला गया कि caller वही process है जिसने उसे खोला था।
यह वही tool है, दोनों भाषाओं में, साथ-साथ registered:
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.")पहले यह पढ़िए कि एक जैसा क्या है, क्योंकि finding वही है। दोनों एक name, एक description, दो described string arguments और तीन annotations declare करते हैं; दोनों एक function हैं; कोई भी JSON-RPC, framing, stdout या protocol version का ज़िक्र नहीं करता। दोनों SDKs एक ही shape पर converged हुए, और “Tier 1” का मतलब यही होना चाहिए।1
दो फर्क असली हैं और दोनों बाद में लौटते हैं। TypeScript arguments को एक schema library—यहाँ Zod—से describe करता है, और schema एक value है जिसे आप लिखते हैं। Python उन्हें function के अपने type hints से describe करता है और import time पर पढ़ता है, इसलिए वह function के बारे में वे बातें जानता है जो TypeScript file ने उसे कभी बताई ही नहीं। और error path: TypeScript isError के साथ tool result लौटाता है, Python raise करता है। इसे याद रखिए।
बाकी चार registrations में कोई structural फर्क नहीं है। Resource server.registerResource("open-incidents", "incidents://open", …) बनाम @server.resource("incidents://open", …) है; prompt registerPrompt बनाम @server.prompt है। हर file की आखिरी line transport है: await server.connect(new StdioServerTransport()) बनाम server.run()।
पूरी files: TypeScript की 81 non-blank lines और 3,060 bytes, Python की 63 और 2,555। इसे उतने ही नमक के साथ लीजिए जितना चाहिए—line counts formatter को उतना ही मापते हैं जितना भाषा को, इसलिए नीचे headline table में दोनों में से कोई number नहीं है।
एक client, दोनों servers
सेक्शन का लिंक: एक client, दोनों serversभाषा invisible है—इसका proof है एक client, दो बार run किया गया, ग्यारह lines में:
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 पर point करें। Real output, trimmed:
$ 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}"}]वही tools, वही order, वही handle। TypeScript client नहीं बता सकता कि server किसमें लिखा है, और वह पूछता भी नहीं। यही protocol का पूरा वादा है—और वह कायम है।
अब दूसरे result की whitespace देखिए, क्योंकि वह cosmetic नहीं है: Python SDK payloads को pydantic_core.to_json(result, fallback=str, indent=2) के साथ serialise करता है। Resource read पर, जब list में दो incidents हैं, TypeScript body 136 characters और 37 o200k_base tokens है; Python body 185 और 62। Identical rows के लिए अड़सठ प्रतिशत ज़्यादा tokens, हर बार, उस व्यक्ति के खर्च पर जो resource को prompt में पढ़ता है।
Catalogue में भी वही कहानी है, बस वजह बड़ी है। दोनों servers, वही तीन tools, tools/list key by key तौला गया:
| key | TypeScript | Python |
|---|---|---|
name | 21 | 21 |
description | 46 | 46 |
annotations | 46 | 46 |
inputSchema | 211 | 192 |
outputSchema | — | 187 |
execution | 27 | — |
| total | 342 | 480 |
Python के input schemas सस्ते हैं—TypeScript का Zod bridge हर एक पर $schema और additionalProperties stamp करता है। पूरा 138-token gap एक output schema है जिसे किसी ने लिखा नहीं। resolve_incident को -> Incident annotate किया गया है, इसलिए SDK ने return type के लिए JSON Schema derive किया और ship कर दिया। यह सचमुच उपयोगी है—यही client को structuredContent validate करने देता है—और यह आपके context window में type hint की वजह से आ रहे 187 tokens हैं। अध्याय 24 का rule कि definitions ज़रूरी material को बाहर धकेल देती हैं, उन schemas पर भी लागू होता है जिनके होने का आपको पता नहीं था।
जानबूझकर तोड़ें: leak हुआ error message
सेक्शन का लिंक: जानबूझकर तोड़ें: leak हुआ error messageऊपर के दो error paths style choice नहीं हैं। हर server को ऐसा tool दीजिए जो वैसे fail हो जैसे real 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 ने model के context में एक internal address, एक port, एक database name और एक service account डाल दिया। Python SDK ने इनमें से कुछ भी वहाँ नहीं डाला; traceback stderr में गया और server पर ही रहा।
इनमें से कोई bug नहीं है। दोनों decisions हैं, और Python वाला अपने docstring में लिखा है: ToolError “एक failure है जिसकी आपने अपेक्षा की थी” और उसका message “model के पढ़ने के लिए content में” लौटाया जाता है; बाकी कुछ भी “crash की तरह treat होता है: model सिर्फ Error executing tool <name> देखता है, और server traceback को ERROR पर logs करता है।” Crash case की class बाकी बात खुलकर कह देती है—“original में से कुछ भी client तक नहीं पहुँचता।”
दोनों behaviours आधे समय गलत हैं। अध्याय 18 ने तर्क दिया था कि validation error ऐसे tool result के रूप में वापस आना चाहिए जिसे model पढ़कर correct कर सके, क्योंकि अधिकांश integrations में वही सबसे high-leverage line होती है; Python side पर इसके लिए ToolError explicitly raise करना पड़ता है, और bare ValueError useful sentence को फेंक देता है। अध्याय 30 का तर्क उलटी दिशा में जाता है: tool जो भी लौटाता है वह ऐसे context में उतरता है जिसे बाद की prompt injection वापस पढ़ने की कोशिश कर सकती है, और unreviewed exception string आपके system का सबसे कम audited text है।
दोनों से बचने वाला rule: हर tool के लिए तय करें कि failure को क्या कहने की अनुमति है, और वह string खुद लिखें। किसी भी भाषा में exception के default text को फैसला न करने दें।
जानबूझकर तोड़ें: standard output पर एक line
सेक्शन का लिंक: जानबूझकर तोड़ें: standard output पर एक lineOfficial tutorial rule को बिना हिचक कहता है: “STDIO-based servers के लिए: stdout पर कभी न लिखें। stdout पर लिखना JSON-RPC messages को corrupt कर देगा और आपका server तोड़ देगा। print() function default रूप से stdout पर लिखता है, इसलिए उसे STDIO server से पूरी तरह बाहर रखें।”1 अध्याय 26 ने normative version quote किया था—server “अपने stdout पर ऐसा कुछ भी MUST NOT write करे जो valid MCP message नहीं है।”2
हर server में एक line जोड़िए और 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 startingPython वाला ज़्यादा खराब है, और वजह MCP नहीं है। जिस process का stdout terminal नहीं बल्कि pipe है, उसे block-buffered stream मिलता है, इसलिए stray line तब flush होती है जब buffer decide करता है—यहाँ, exit पर, उस response के बाद जिसके पहले वह लिखी गई थी। Corruption वहाँ नहीं दिखता जहाँ bug है। flush=True जोड़िए, या कोई library जो flush करती है, और वह move कर जाता है।
फिर वह हिस्सा जो समझाता है कि यह ship क्यों हो जाता है। Broken server को तीन clients में feed करें:
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सात-line parser तुरंत मर जाता है। Official client और Inspector दोनों कंधे उचकाते हैं—वे line skip करते हैं और आगे बढ़ते हैं। जो rule सिर्फ उन clients को तोड़ता है जिन्हें कोई use नहीं करता, वह production तक intact पहुँचता है; इसलिए इसे यहाँ जानबूझकर तोड़ना customer के log में तोड़ने से बेहतर है।
Inspector का CLI mode वह आधा हिस्सा है जिसे भुला दिया जाता है: npx @modelcontextprotocol/inspector --cli <command> --method tools/list catalogue print करता है और exit करता है, जिससे वह browser UI की तुलना में scriptable हो जाता है।3
दोनों SDKs clean install हुए, अपनी-अपनी directories में, कुछ shared नहीं:
| TypeScript | Python | |
|---|---|---|
| package | @modelcontextprotocol/sdk 1.30.0 + zod 3.25.76 | mcp 2.1.1 |
| latest protocol revision implemented | 2025-11-25 | 2026-07-28 |
| transitive packages installed | 94 | 28 |
| installed size | 13.9 MiB | 44.3 MiB |
| files on disk | 3,386 | 2,018 |
| third-party packages loaded to serve stdio | 8 of 94 | 18 of 28 |
| bare interpreter start, median | 19.4 ms | 11.1 ms |
spawn → tools/list answered, median of 25 | 144.5 ms | 709.4 ms |
tools/list catalogue, o200k_base tokens | 342 | 480 |
हर row अलग दिशा में चौंकाती है, इसलिए comparison assume करने के बजाय चलाने लायक है।
TypeScript तीन गुना से ज़्यादा packages install करता है और एक-तिहाई से कम bytes। 94 dependencies npm ecosystem का अपने जैसा होना है—fast-deep-equal, es-errors, dunder-proto। Python के 28 कम हैं और विशाल हैं: cryptography, pydantic-core और uvicorn compiled artefacts हैं। अगर आपकी instinct यह है कि dependency count चिंता की चीज़ है, तो यह row counter-example है।
Python का interpreter Node से तेज़ start होता है, और फर्क मामूली नहीं है—empty program पर 19.4 ms के मुकाबले 11.1 ms। इसलिए cold-start row के 565 ms भाषा नहीं हैं। वह SDK है, और loaded-packages row बताती है क्यों:
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 है, वह अपनी पहली line पढ़ने से पहले ASGI web server, HTTP client और TLS library import करता है। TypeScript SDK भी Express, Hono, jose और eventsource ship करता है—वे disk पर unread पड़े रहते हैं, क्योंकि package boundary उन्हें server/stdio.js import से बाहर रखती है। Python का package एक import graph है, इसलिए import mcp यानी सब कुछ: python -X importtime import mcp.server.mcpserver को 727 ms attribute करता है—यह figure import profiler के नीचे मापा गया, इसलिए spawn से answer तक unprofiled run के 709 ms से ऊपर आता है—और उनमें से 269 mcp.types subtree को—wire types Pydantic models हैं, हर protocol message per revision के लिए एक class, और उन्हें बनाना import पर किया गया काम है। यह design trade है, sloppiness नहीं—eager imports ही वजह हैं कि Python SDK अगली line पर बिना दूसरे install के आपको run(transport="streamable-http") दे सकता है।
और फिर opening block की आखिरी row तर्क को उलट देती है। TypeScript server को ठीक से package करें—bin entry, shebang, npm link, download करने को कुछ नहीं—और उसे npx के through --no-install के साथ launch करें, जो published stdio server शुरू करने का वास्तविक तरीका है:
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 लेता है—पूरे TypeScript SDK import से साढ़े चार गुना—और यह हर launch पर चुकाया जाता है, क्योंकि MCP host stdio server को वह command run करके शुरू करता है। तो “TypeScript पाँच गुना तेज़ start होता है” का honest रूप है: हाँ, जब तक आप उसे normal तरीके से distribute नहीं करते। यही caveat शायद uvx पर भी लागू होता है; इस machine पर uv installed नहीं था, इसलिए वह row मौजूद नहीं है। Unmeasured कुछ भी table में नहीं जाता।
दो transports, और सिर्फ दो
सेक्शन का लिंक: दो transports, और सिर्फ दोअध्याय 26 ने stdio की framing cover की थी। दो चीज़ें उसने यहाँ के लिए छोड़ीं।
पहली: server को npx या uvx के साथ run करना stdio transport ही है। कोई अलग “package mode” नहीं है। Host की configuration command और arguments नाम देती है; host उसे spawn करता है और pipes पर बात करता है। इसलिए locally “मैं इसे distribute कैसे करूँ” और “यह कौन सा transport बोलता है” एक ही सवाल हैं, और launcher's cost shipping वाले chapter में आता है।
दूसरी: stdio में authorization section बिल्कुल नहीं है, और specification इसे एक line में कहती है—stdio use करने वाली implementations “इस specification का पालन SHOULD NOT करें, और इसके बजाय environment से credentials retrieve करें।”4 इसका security model operating system का है, और limit भी वही: local subprocess ठीक एक machine और एक user को serve करता है।
दूसरा live transport है Streamable HTTP: एक single endpoint जो POST accept करता है, हर JSON-RPC message के लिए एक HTTP request, और Accept header जिसमें application/json और text/event-stream दोनों list होने चाहिए क्योंकि server हर request पर चुनता है कि वह दोनों में से किससे answer करेगा।5 अध्याय 14 ने उस event stream को हाथ से parse किया था, इसलिए wire format में कुछ नया नहीं—सिर्फ उसे wrap करने वाली चीज़ें नई हैं। Current revision की तीन obligations आसानी से छूट जाती हैं और तीनों testable हैं:
Version header को body से agree करना होगा
सेक्शन का लिंक: Version header को body से agree करना होगाहर POST MCP-Protocol-Version carry करता है, और उसकी value request के अपने _meta के अंदर protocolVersion से match करनी चाहिए। Mismatch shrug नहीं, header-mismatch error के साथ 400 है।5
Compliance के लिए दो और headers required हैं
सेक्शन का लिंक: Compliance के लिए दो और headers required हैंMcp-Method हर request पर method mirror करता है; Mcp-Name tools/call, resources/read और prompts/get पर params.name या params.uri mirror करता है। वे इसलिए हैं ताकि proxy bodies parse किए बिना route कर सके।5
पुराने shapes चले गए हैं, और refusal से answer करते हैं
सेक्शन का लिंक: पुराने shapes चले गए हैं, और refusal से answer करते हैंGET stream, Mcp-Session-Id और Last-Event-ID resumption सब remove कर दिए गए। जो server सिर्फ यह revision बोलता है, उसे GET या DELETE पर 405 Method Not Allowed answer करना चाहिए, session header को mint किए बिना ignore करना चाहिए, और Last-Event-ID ignore करना चाहिए।5
अब वह measurement जो पूरे chapter को reframe करता है। हर server को HTTP पर current-revision request भेजें।
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)"}}Constants behaviour से agree करते हैं: Python SDK का LATEST_PROTOCOL_VERSION 2026-07-28 पढ़ता है, TypeScript SDK का 2025-11-25। ऊपर वाले step की header-mismatch request भेजें और Python server error -32020 और message “mcp-protocol-version header does not match the request envelope's protocol version” के साथ 400 answer करता है; TypeScript SDK में ऐसा code नहीं है, क्योंकि वह इसे define करने वाली revision implement नहीं करता।
जो page दोनों को Tier 1 पर list करता है वह यह भी कहता है “Each SDK provides the same functionality”।1 नीचे दी तारीख पर, current revision के लिए, वह sentence aspirational है। जिस SDK को आप install करने वाले हैं उसमें LATEST_PROTOCOL_VERSION check करें; यह एक line है, और इस chapter का अकेला claim है जो एक साल बाद भी मायने रखेगा।
401, और quote करने लायक sentence
सेक्शन का लिंक: 401, और quote करने लायक sentenceServer को laptop से हटाइए और किसी stranger का client token लेकर आ जाता है। यह वह आधा है जिसे अध्याय 26 ने छोड़ दिया था और जिसे multi-user product skip नहीं कर सकता।
Specification MCP server को OAuth 2.1 role में रखती है और उसका नाम देती है: protected MCP server एक resource server है, client OAuth client है, और authorization server किसी और की समस्या है।4 उस role से चार mandatory clauses, पूरे quote किए गए क्योंकि इन्हें 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” anti-passthrough rule है, और इसी वजह से पूरा audience apparatus मौजूद है। जो server उसे दिए गए bearer token को third-party API पर replay करता है, वह confused deputy है: वह अपनी trust उसे उधार दे देता है जिसने उसे call किया। Rule reuse को forbid करता है, सिर्फ storage को नहीं।
इसे enforceable बनाने में चार RFCs लगते हैं, हर एक का एक काम।6 RFC 9728 client को authorization server खोजने देता है: MCP server protected-resource-metadata document serve करता है और 401 उसकी तरफ point करता है। RFC 8707 resource parameter है—client को authorization request और token request दोनों में server की canonical URI भेजनी होगी, “regardless of whether authorization servers support it”, ताकि issued token अपनी audience name करे। RFC 9207 दूसरी तरफ से loop बंद करता है: client redirect करने से पहले issuer record करता है और returned iss को exact string से compare करता है, बिना normalisation—न case folding, न default-port elision, न trailing slash। और RFC 7591, Dynamic Client Registration, अब Client ID Metadata Documents के पक्ष में deprecated है, “retained for backwards compatibility with authorization servers that do not support” them।4
दोनों servers पर इसे ऐसे token verifier के साथ wire करें जो audience check करने के अलावा कुछ नहीं करता। TypeScript ladder:
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"}दोनों SDKs वह document serve करते हैं और दोनों एक 401 को उसकी तरफ point करते हैं, यही पूरी discovery story है: जिस client ने आपका server कभी नहीं देखा, वह refusal से सीखता है कि authenticate कहाँ करना है। 403 अलग चीज़ है—token ठीक है, scope नहीं—और challenge बताता है क्या missing है ताकि client start over करने के बजाय step up कर सके।
दो rungs अलग हैं, और कोई भी फर्क specification में नहीं है। TypeScript SDK बिना expiry claim वाले token को refuse करता है; Python वाला 200 लौटाता है, क्योंकि expires_at उसके AccessToken पर optional है और None का मतलब है “no opinion”। और Python 403 error_description="Required scope: incidents:read" carry करता है, उस scope parameter के बिना जिसे specification कहती है कि servers को include करना चाहिए। Verifier library default accept करने की जगह नहीं है: audience check किसी भी भाषा में आपको लिखना है, और expiry भी।
उसी run से एक honest nit। Endpoint पर GET ने Express wiring पर 404 और Python वाले पर 400 Bad Request: Missing session ID answer किया, जहाँ specification 405 Method Not Allowed माँगती है और जहाँ “session ID” वह vocabulary है जिसे इस revision ने remove कर दिया। कोई dangerous नहीं है; दोनों mid-migration ecosystem की shape हैं।
Servers सच में कहाँ रहते हैं
सेक्शन का लिंक: Servers सच में कहाँ रहते हैंShipping का आखिरी टुकड़ा है कि आप publish कहाँ करते हैं, और इसका एक number वाला answer है। आज crawl किया गया, official registry में हर server उसके latest version पर:7
| servers | |
|---|---|
| total (latest version, not deleted) | 28,170 |
| active / deprecated | 27,853 / 317 |
| ship at least one installable package | 13,065 |
| remote only — a URL, nothing to install | 14,696 |
| npm | 8,275 |
| PyPI | 3,603 |
| OCI images | 867 |
mcpb bundles | 706 |
| NuGet / Cargo | 107 / 43 |
दो readings, उलटी दिशाओं में इशारा करती हुईं। Published servers के हिसाब से npm 2.3 to 1 से आगे है—यह वह number है जिसे लोग ecosystem को TypeScript कहने पर quote करते हैं। Downloads के हिसाब से Python आगे है: पिछले तीस दिनों में mcp ने @modelcontextprotocol/sdk के 194.7 million के मुकाबले 286.7 million लिए, fastmcp के 72.1 million जोड़ने से पहले।7 दोनों Tier 1 हैं, normative schema schema.ts है, और official “Build an MCP server” tutorial Python tab पर खुलता है।1 इनमें से जो आधा आपके दिमाग में था, दूसरा आधा भी सच है।
और वह row जो दोनों से ज़्यादा मायने रखती है: आधी से ज़्यादा registry—28,170 में से 14,696—में install करने को कुछ नहीं है। वे web services हैं। Transport tallies दूसरी तरफ से agree करते हैं: 14,290 package entries में से 13,787 stdio declare करती हैं; 16,640 remote entries में से 15,570 Streamable HTTP declare करती हैं और 1,070 अभी भी deprecated HTTP+SSE declare करती हैं। इसलिए “MCP server आपके laptop पर subprocess है” shrinking minority को describe करता है, और उन 14,696 में से हर एक को environment variable के बजाय ऊपर वाला section चाहिए।
विवरण दिखाएँ
जानबूझकर bilingual, और इसकी precedent।
Course में यह अकेला bilingual chapter है, क्योंकि honest answer split होता है: registry npm-first है और downloads Python-first हैं, एक ही समय पर, आज। दोनों में से एक लिखना सवाल का आधा हिस्सा छोड़ देना और ecosystem को गलत describe करना होता। Open में precedent है—Hugging Face MCP Course अपनी prerequisites में “Experience with at least one programming language (Python or TypeScript examples will be shown)” list करता है, और दोनों सिखाता है। जिस protocol की पूरी value implementations की संख्या है, वह monolingual होने की खराब जगह है।
Dated section: ऊपर की हर बात जिसकी shelf life है
सेक्शन का लिंक: Dated section: ऊपर की हर बात जिसकी shelf life है7 September 2026 को पढ़ा और मापा गया, protocol revision 2026-07-28 के खिलाफ।
| value | |
|---|---|
@modelcontextprotocol/sdk | 1.30.0, published 27 July 2026; 4,322,438 bytes unpacked, 693 files, 17 direct dependencies |
| latest revision it implements | 2025-11-25 |
mcp (PyPI) | 2.1.1, published 25 August 2026; 357,912-byte wheel, plus mcp-types 2.1.1 at 69,656 bytes |
| latest revision it implements | 2026-07-28 |
| SDK tiers | TypeScript, Python, C#, Go, Rust at Tier 1; Java, Ruby at Tier 2; Swift, PHP, Kotlin at Tier 3 |
| registry servers | 28,170 |
| downloads, last 30 days | mcp 286,653,871 · fastmcp 72,097,269 · @modelcontextprotocol/sdk 194,679,333 |
एक migration note जो number नहीं है। mcp 2.x में, FastMCP का नाम बदलकर MCPServer कर दिया गया, और online लगभग हर tutorial अभी भी पुराने import से खुलता है। SDK एक module ship करता है जिसका अकेला purpose यही समझाना है, और यह इस chapter का सबसे 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.तो कौन सा
सेक्शन का लिंक: तो कौन साTable सामने हो तो recommendation boring है, और यह अच्छा sign है।
अगर server उस web application के अंदर रहता है जिसे आप पहले से run करते हैं, तो उसे TypeScript में लिखें। वही process, वही deploy, वही request handler; Streamable HTTP एक endpoint है जिसे आप बाकी endpoints के बगल में जोड़ते हैं; और 13.9 MiB और 145 ms free हैं क्योंकि runtime पहले से up था। यह 14,696 remote servers में से अधिकांश है।
अगर server data tooling wrap करता है, तो उसे Python में लिखें। आप expose कर रहे हैं pandas, warehouse client, notebook भर transforms, और दूसरी भाषा में server एक subprocess call होगा जिसने schema पहन रखा है। जो service एक बार start होती है उसमें सात सौ milliseconds का import cost नहीं है; ऐसे subprocess में है जिसे host दिन भर relaunch करता है।
और फिलहाल revision row दोनों पर भारी है। अगर आपको 2026-07-28 चाहिए—multi-round-trip requests, resultType, cache hints, server/discover—तो दोनों SDKs में से एक के पास यह आज है और दूसरे के पास नहीं।
यह आगे कहाँ जाता है
सेक्शन का लिंक: यह आगे कहाँ जाता हैअब आप वही server किसी भी भाषा में ship कर सकते हैं, choice को preference के बजाय table से defend कर सकते हैं, उसे दोनों live transports पर run कर सकते हैं, और उसे ऐसा token दे सकते हैं जिसे वह refuse करेगा।
आपने जो बनाया है वह अभी भी एक function है: schema, endpoint, deterministic चीज़ जिसे model invoke करता है। Knowledge की पूरी एक class उस shape में fit नहीं होती—हम postmortem कैसे लिखते हैं, हमारी incident reports को कौन से fields चाहिए, हम चीज़ें किस order में करते हैं और क्यों। यह procedure है, prose है, और इसे tool description में force करना ही system prompts को दो हजार tokens तक बढ़ाता है, जो हर single turn पर चुकते हैं चाहे conversation incidents के बारे में हो या नहीं।
अध्याय 28 दूसरा answer है: एक folder जिसके अंदर SKILL.md है जिसे model call करने के बजाय पढ़ता है, तीन levels में loaded ताकि reference material की cost लगभग nothing रहे जब तक वह turn न आ जाए जिसमें इसकी ज़रूरत है। इसकी कोई main language नहीं है, और यही पहली चीज़ है जो यह सिखाता है।
Sources and method
सेक्शन का लिंक: Sources and methodयहाँ सब कुछ 7 September 2026 को Node 22.22.3 और Python 3.14.4 पर, @modelcontextprotocol/sdk 1.30.0 with zod 3.25.76 और mcp 2.1.1 के खिलाफ मापा गया, प्रत्येक को अपनी throwaway directory में install करके। Timings 25 launches के medians हैं, spawn से tools/list response वाली line तक wall clock; token counts हर definition के JSON पर tiktoken के through o200k_base हैं। कोई paid API call नहीं की गई: यहाँ किसी चीज़ को model की ज़रूरत नहीं।
दो servers 81 और 63 non-blank lines हैं; उनके तीन tools में से एक ऊपर दोनों भाषाओं में reproduce किया गया है, और बाकी चार registrations केवल described तरीके से differ करते हैं। Python SDK की error-disclosure policy mcp/server/mcpserver/exceptions.py में ToolError और UnexpectedToolError के docstrings से quote की गई है; pretty-printing default mcp/server/mcpserver/resources/types.py और utilities/func_metadata.py में pydantic_core.to_json(result, fallback=str, indent=2) है। Protocol-version constants mcp_types/version.py में LATEST_PROTOCOL_VERSION और TypeScript SDK के types.js में हैं, दोनों changelog के बजाय installed packages से पढ़े गए।
संदर्भ
सेक्शन का लिंक: संदर्भ-
SDKs,
modelcontextprotocol.io/docs/sdk, और Build an MCP server,modelcontextprotocol.io/docs/develop/build-server, both read 7 September 2026. Tier table का source, sentence “Each SDK provides the same functionality but follows the idioms and best practices of its language” का source, tutorial के language-tab order (Python, TypeScript, Java, Kotlin, C#, Ruby, Rust, Go) का source, औरprint()तथाstdoutके बारे में quoted logging rule का source। ↩ ↩2 ↩3 ↩4 -
stdio transport,
.../basic/transports/stdio. Newline framing औरstdoutpurity rule का source। अध्याय 26 इस page को पूरा पढ़ता है; यहाँ इसे उस line के लिए cite किया गया है जिसका broken server उल्लंघन करता है। ↩ -
MCP Inspector,
modelcontextprotocol.io/docs/2026-07-28/tools/inspector, read 7 September 2026. एक package, एक binary के पीछे तीन clients—web,--cliऔर--tui—जो एक core, transports का एक set और disk पर एक OAuth state share करते हैं। CLI ने यहाँ catalogue traces produce किए। ↩ -
Authorization,
modelcontextprotocol.io/specification/2026-07-28/basic/authorization, read 7 September 2026. Resource-server role का source; ऊपर पूरी quote की गई चार token-handling clauses; यह requirement कि servers RFC 9728 implement करें और clients discovery के लिए उसका use करें;resourceparameter rules और canonical-URI definition; issuer-validation table; Dynamic Client Registration का deprecation;401/403/400table औरinsufficient_scopechallenge; और stdio exemption, “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 rule, dualAcceptrequirement,MCP-Protocol-Versionheader और उसका must-match-the-body rule, “REQUIRED for compliance” के रूप में describedMcp-MethodऔरMcp-Nameheaders, GET stream, sessions औरLast-Event-IDका removal,405guidance, mandatoryOriginvalidation, और SEP-2596 के तहत 2024-11-05 HTTP+SSE transport को Deprecated classify करने का source। ↩ ↩2 ↩3 ↩4 -
वे चार जिन पर specification lean करती है, उस 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, February 2020 —resourceparameter और वह audience जिसे वह bind करता है। Jones, M.B., Hunt, P. and Parecki, A., OAuth 2.0 Protected Resource Metadata, RFC 9728, April 2025 — वह document जिसकी तरफ401point करता है। Meyer zu Selhausen, K. and Fett, D., OAuth 2.0 Authorization Server Issuer Identification, RFC 9207, March 2022 —issparameter और exact-string comparison। Richer, J. (ed.) et al., OAuth 2.0 Dynamic Client Registration Protocol, RFC 7591, July 2015, इस use के लिए deprecated। और Jones, M. and Hardt, D., The OAuth 2.0 Authorization Framework: Bearer Token Usage, RFC 6750, October 2012, section 3, ऊपर वालेWWW-Authenticatechallenge shape के लिए। ↩ -
Official MCP registry,
registry.modelcontextprotocol.io/v0/servers, crawled 7 September 2026 withversion=latest: 282 pages, 28,170 servers, distinct server names परregistryTypeद्वारा tallied। Download figures:@modelcontextprotocol/sdkके लिएapi.npmjs.org/downloads/point/last-month(194,679,333 for 8 August – 6 September 2026) औरmcpतथाfastmcpके लिएpypistats.org/api/packages/<name>/recent, दोनों same day read। Package sizes npm registry document और PyPI JSON API से आए हैं। ↩ ↩2