Lewati ke konten
27/30Bab 27 dari 30

Rilis MCP Server: TypeScript dan Python, Terukur

Server yang sama ditulis dua kali — tiga tool, satu resource, satu prompt — lalu ditimbang. 94 package vs 28, cold start 145 ms vs 709.

Di halaman ini

Inilah seluruh argumen bahasa itu, diukur, sebelum satu kata pun dibuat.

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

Dua baris pertama adalah perbandingan yang semua orang inginkan. Baris ketiga adalah server TypeScript yang sama dari baris pertama, diluncurkan sebagaimana ia benar-benar akan didistribusikan — dan hasilnya hanya terpaut tiga milidetik dari Python.

Chapter 26 membaca Model Context Protocol terhadap spesifikasinya sendiri dengan JSON-RPC mentah, karena JSON-RPC mentah tidak punya bahasa. Chapter ini punya dua, dan bobot argumennya jatuh di sini: server yang sama, ditulis dua kali. Tiga tools, satu resource, satu prompt, kedua SDK, tanpa jalan pintas di kedua sisi. Lalu transports, inspector, 401, dan angka-angka yang belum pernah dipublikasikan siapa pun.

Server-nya, dan mengapa ia memuat lima hal ini

Tautan ke bagian: Server-nya, dan mengapa ia memuat lima hal ini

Sebuah log insiden. Tiga tools, karena pemisahan Chapter 18 antara read dan write harus terlihat: search_incidents membaca, open_incident menulis dan mengembalikan handle, resolve_incident mengambil handle itu dan menutup. Satu resource, incidents://open, karena membaca daftar saat ini adalah sesuatu yang ditempelkan oleh application. Satu prompt, postmortem, karena “tuliskan ini” adalah slash command milik seseorang. Itulah hierarki kontrol Chapter 26 — model, application, person — yang diubah menjadi lima registrasi.

Handle itu lebih penting daripada kelihatannya. Chapter 26 merusak kalender mainan dengan menyimpan state-nya di array tingkat module: protokol tidak punya session, jadi tool pembuatan mengembalikan identifier opaque dan setiap call berikutnya menerimanya sebagai argumen biasa. Tidak ada dalam kedua file yang berasumsi bahwa caller adalah process yang membukanya.

Ini tool yang sama dalam kedua bahasa, didaftarkan berdampingan:

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.")

Baca dulu apa yang sama, karena itulah temuannya. Keduanya mendeklarasikan name, description, dua argumen string yang dijelaskan, dan tiga annotations; keduanya satu function; tidak ada yang menyebut JSON-RPC, framing, stdout, atau protocol version. Kedua SDK bertemu pada bentuk yang sama, dan itulah arti “Tier 1” yang seharusnya.1

Dua perbedaan nyata dan keduanya muncul lagi nanti. TypeScript menjelaskan argumen dengan schema library — Zod di sini — dan schema itu adalah value yang kamu tulis. Python menjelaskannya dengan type hints milik function sendiri dan membacanya saat import, sehingga ia mengetahui hal-hal tentang function yang tidak pernah diberi tahu oleh file TypeScript. Dan jalur error: TypeScript mengembalikan tool result dengan isError, Python melempar. Simpan itu.

Empat registrasi lainnya tidak berbeda secara struktural. Resource-nya adalah server.registerResource("open-incidents", "incidents://open", …) dibanding @server.resource("incidents://open", …); prompt-nya registerPrompt dibanding @server.prompt. Baris terakhir tiap file adalah transport: await server.connect(new StdioServerTransport()) dibanding server.run().

File utuh: 81 baris non-kosong dan 3.060 byte TypeScript dibanding 63 dan 2.555. Ambil itu dengan garam secukupnya — jumlah baris mengukur formatter sama banyaknya dengan bahasa, karena itu tidak satu pun angka masuk ke tabel headline di bawah.

Bukti bahwa bahasa tidak terlihat adalah satu client yang dijalankan dua kali, dalam sebelas baris:

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));

Arahkan ke tiap server bergantian. Output nyata, dipangkas:

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}"}]

Tools yang sama, urutan yang sama, handle yang sama. Client TypeScript tidak bisa tahu server ditulis dengan apa, dan ia tidak pernah bertanya. Itulah seluruh janji sebuah protokol, terpenuhi.

Sekarang lihat whitespace di result kedua, karena itu bukan kosmetik: Python SDK menserialisasi payload dengan pydantic_core.to_json(result, fallback=str, indent=2). Pada pembacaan resource dengan dua insiden dalam daftar, body TypeScript 136 karakter dan 37 token o200k_base; body Python 185 dan 62. Enam puluh delapan persen lebih banyak token untuk baris identik, dibayar oleh siapa pun yang membaca resource ke dalam prompt, setiap kali.

Catalogue punya cerita yang sama dengan penyebab lebih besar. Kedua server, tiga tools yang sama, tools/list ditimbang key demi key:

keyTypeScriptPython
name2121
description4646
annotations4646
inputSchema211192
outputSchema187
execution27
total342480

Schema input Python lebih murah — bridge Zod milik TypeScript menempelkan $schema dan additionalProperties pada masing-masing. Seluruh selisih 138-token adalah output schema yang tidak ditulis siapa pun. resolve_incident dianotasi -> Incident, jadi SDK menurunkan JSON Schema untuk return type dan mengirimkannya. Itu benar-benar berguna — itulah yang memungkinkan client memvalidasi structuredContent — dan itu adalah 187 token dari context window kamu yang datang karena sebuah type hint. Aturan Chapter 24 tentang definisi yang mendesak materi penting juga berlaku untuk schema yang tidak kamu tahu kamu punya.

Rusak dengan sengaja: pesan error yang bocor

Tautan ke bagian: Rusak dengan sengaja: pesan error yang bocor

Dua jalur error di atas bukan pilihan gaya. Beri tiap server tool yang gagal seperti integrasi nyata gagal, lalu baca apa yang sampai ke 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}

TypeScript SDK memasukkan alamat internal, port, nama database, dan service account ke context model. Python SDK tidak memasukkan satu pun; traceback pergi ke stderr dan tetap di server.

Keduanya bukan bug. Keduanya keputusan, dan keputusan Python tertulis dalam docstring-nya sendiri: ToolError adalah “kegagalan yang kamu antisipasi” dan pesannya dikembalikan “di content untuk dibaca model”; apa pun selain itu “diperlakukan sebagai crash: model hanya melihat Error executing tool <name>, dan server mencatat traceback di ERROR”. Class untuk kasus crash mengatakannya terang-terangan — “tidak ada apa pun dari yang asli sampai ke client”.

Kedua perilaku salah separuh waktu. Chapter 18 berargumen bahwa validation error harus kembali sebagai tool result yang dapat dibaca dan diperbaiki model, karena itu baris dengan leverage tertinggi di sebagian besar integrasi; di sisi Python itu mengharuskan menaikkan ToolError secara eksplisit, dan ValueError polos membuang kalimat yang berguna. Argumen Chapter 30 berjalan ke arah sebaliknya: semua yang dikembalikan tool mendarat di context yang dapat coba dibaca ulang oleh prompt injection berikutnya, dan string exception yang tidak ditinjau adalah teks paling minim audit di sistem kamu.

Aturan yang bertahan dari keduanya: putuskan, per tool, apa yang boleh dikatakan sebuah kegagalan, dan tulis string itu sendiri. Jangan pernah biarkan teks default exception yang memutuskan, dalam bahasa apa pun.

Rusak dengan sengaja: satu baris di standard output

Tautan ke bagian: Rusak dengan sengaja: satu baris di standard output

Tutorial resmi menyatakan aturannya tanpa ragu: “Untuk server berbasis STDIO: Jangan pernah menulis ke stdout. Menulis ke stdout akan merusak pesan JSON-RPC dan mematahkan server kamu. Function print() menulis ke stdout secara default, jadi jauhkan sepenuhnya dari server STDIO.”1 Chapter 26 mengutip versi normatifnya — server “MUST NOT menulis apa pun ke stdout miliknya yang bukan pesan MCP valid”.2

Tambahkan satu baris ke tiap server dan baca raw 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

Yang Python lebih buruk, dan alasannya bukan MCP. Process dengan stdout berupa pipe alih-alih terminal mendapatkan stream block-buffered, sehingga baris liar itu di-flush kapan pun buffer memutuskan — di sini, saat exit, setelah response yang sebenarnya ditulis sebelumnya. Korupsi tidak muncul di tempat bug berada. Tambahkan flush=True, atau library yang flush, dan ia berpindah.

Lalu bagian yang menjelaskan mengapa ini bisa ship. Beri server rusak ke tiga client:

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 tujuh baris langsung mati. Client resmi dan Inspector sama-sama mengangkat bahu — mereka melewati baris itu dan lanjut. Aturan yang hanya merusak client yang tidak dipakai siapa pun adalah aturan yang sampai ke production secara utuh, sehingga layak dirusak dengan sengaja di sini, bukan di log pelanggan.

Mode CLI Inspector adalah separuh yang sering terlupakan: npx @modelcontextprotocol/inspector --cli <command> --method tools/list mencetak catalogue dan keluar, sehingga bisa di-script dengan cara yang tidak bisa dilakukan browser UI.3

Kedua SDK terpasang bersih, ke direktori masing-masing, tanpa berbagi apa pun:

TypeScriptPython
package@modelcontextprotocol/sdk 1.30.0 + zod 3.25.76mcp 2.1.1
revisi protokol terbaru yang diimplementasikan2025-11-252026-07-28
transitive packages terpasang9428
ukuran terpasang13,9 MiB44,3 MiB
file di disk3.3862.018
third-party packages yang dimuat untuk melayani stdio8 dari 9418 dari 28
bare interpreter start, median19,4 ms11,1 ms
spawn → tools/list dijawab, median dari 25144,5 ms709,4 ms
catalogue tools/list, token o200k_base342480

Setiap baris mengejutkan ke arah berbeda, sehingga perbandingan ini layak dijalankan, bukan diasumsikan.

TypeScript memasang lebih dari tiga kali jumlah package dan kurang dari sepertiga byte. 94 dependencies adalah ekosistem npm menjadi dirinya sendiri — fast-deep-equal, es-errors, dunder-proto. 28 milik Python lebih sedikit dan sangat besar: cryptography, pydantic-core, dan uvicorn adalah artefak terkompilasi. Jika nalurimu mengatakan dependency count adalah hal yang perlu dikhawatirkan, baris ini adalah kontra-contohnya.

Interpreter Python start lebih cepat daripada Node, dan selisihnya jauh — 11,1 ms melawan 19,4 ms pada program kosong. Jadi 565 ms di baris cold-start bukan bahasanya. Itu SDK-nya, dan baris loaded-packages menjelaskan alasannya:

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, …

Server yang satu-satunya I/O adalah pipe mengimport ASGI web server, HTTP client, dan TLS library sebelum membaca baris pertamanya. TypeScript SDK juga mengirim Express, Hono, jose, dan eventsource — mereka diam di disk tanpa dibaca, karena boundary package menjaganya keluar dari import server/stdio.js. Package Python adalah satu import graph, jadi import mcp adalah semuanya: python -X importtime mengatribusikan 727 ms ke import mcp.server.mcpserver — angka yang diukur di bawah import profiler, karena itu hasilnya di atas 709 ms yang dibutuhkan run tanpa profiler dari spawn sampai menjawab — dan 269 di antaranya ke subtree mcp.types saja — wire types adalah model Pydantic, satu class per protocol message per revisi, dan membangunnya adalah kerja yang dilakukan saat import. Itu trade-off desain, bukan kecerobohan — eager imports adalah alasan Python SDK bisa memberimu run(transport="streamable-http") di baris berikutnya tanpa install kedua.

Lalu baris terakhir dari blok pembuka membatalkan argumennya. Package server TypeScript dengan benar — entry bin, shebang, npm link, tanpa apa pun untuk diunduh — dan jalankan melalui npx dengan --no-install, sebagaimana server stdio yang dipublikasikan benar-benar dimulai:

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 memakan 568 ms per start — empat setengah kali seluruh import TypeScript SDK — dan biaya itu dibayar di setiap launch, karena MCP host memulai server stdio dengan menjalankan command itu. Jadi bentuk jujur dari “TypeScript start lima kali lebih cepat” adalah: memang, sampai kamu mendistribusikannya dengan cara normal. Caveat yang sama mungkin berlaku untuk uvx; mesin ini tidak punya uv terpasang, jadi baris itu tidak ada. Tidak ada yang tidak diukur masuk ke tabel.

Chapter 26 membahas framing stdio. Dua hal ia sisakan untuk di sini.

Pertama: menjalankan server dengan npx atau uvx adalah transport stdio. Tidak ada “package mode” terpisah. Konfigurasi host menyebut command dan arguments; host men-spawn-nya dan berbicara lewat pipe. Karena itu “bagaimana cara mendistribusikan ini” dan “transport mana yang dipakai” adalah satu pertanyaan secara lokal, dan karena itu biaya launcher termasuk dalam chapter tentang shipping.

Kedua: stdio sama sekali tidak punya bagian authorization, dan spesifikasi mengatakannya dalam satu baris — implementasi yang memakai stdio “SHOULD NOT mengikuti spesifikasi ini, dan sebagai gantinya mengambil credentials dari environment”.4 Security model-nya adalah milik operating system, begitu pula batasnya: local subprocess melayani tepat satu mesin dan satu user.

Transport live lainnya adalah Streamable HTTP: satu endpoint yang menerima POST, satu HTTP request per JSON-RPC message, dan header Accept yang harus mencantumkan application/json dan text/event-stream karena server memilih per request mana dari keduanya yang dipakai untuk menjawab.5 Chapter 14 mem-parse event stream itu secara manual, jadi tidak ada yang baru dalam wire format — hanya pembungkusnya. Tiga kewajiban dari revisi saat ini mudah terlewat dan ketiganya bisa diuji:

Setiap POST membawa MCP-Protocol-Version, dan nilainya harus cocok dengan protocolVersion di dalam _meta milik request itu sendiri. Ketidakcocokan adalah 400 dengan error header-mismatch, bukan dibiarkan saja.5

Dua header lagi wajib untuk compliance

Tautan ke bagian: Dua header lagi wajib untuk compliance

Mcp-Method mencerminkan method pada setiap request; Mcp-Name mencerminkan params.name atau params.uri pada tools/call, resources/read, dan prompts/get. Mereka ada agar proxy bisa merutekan tanpa mem-parse body.5

Bentuk lama sudah hilang, dan dijawab dengan penolakan

Tautan ke bagian: Bentuk lama sudah hilang, dan dijawab dengan penolakan

GET stream, Mcp-Session-Id, dan resumption Last-Event-ID semuanya dihapus. Server yang hanya berbicara revisi ini seharusnya menjawab 405 Method Not Allowed untuk GET atau DELETE, mengabaikan session header tanpa membuat yang baru, dan mengabaikan Last-Event-ID.5

Sekarang pengukuran yang membingkai ulang seluruh chapter. Kirim request revisi saat ini ke tiap server lewat 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)"}}

Konstanta sejalan dengan perilaku: LATEST_PROTOCOL_VERSION milik Python SDK membaca 2026-07-28, milik TypeScript SDK membaca 2025-11-25. Kirim request header-mismatch dari langkah di atas dan server Python menjawab 400 dengan error -32020 dan pesan “header mcp-protocol-version tidak cocok dengan protocol version envelope request”; TypeScript SDK tidak punya code seperti itu, karena ia tidak mengimplementasikan revisi yang mendefinisikannya.

Halaman yang mencantumkan keduanya di Tier 1 juga berkata “Setiap SDK menyediakan functionality yang sama”.1 Pada tanggal di bawah, untuk revisi saat ini, kalimat itu aspiratif. Periksa LATEST_PROTOCOL_VERSION di SDK yang akan kamu install; itu satu baris, dan satu-satunya klaim dalam chapter ini yang masih akan penting setahun lagi.

Pindahkan server dari laptopmu dan client orang asing datang dengan token. Ini separuh yang Chapter 26 biarkan, dan separuh yang tidak bisa dilewatkan produk multi-user.

Spesifikasi menempatkan MCP server dalam peran OAuth 2.1 dan menamainya: protected MCP server adalah resource server, client adalah OAuth client, dan authorization server adalah urusan orang lain.4 Dari peran itu, empat klausul wajib, dikutip utuh karena memparafrasakannya adalah cara kesalahan dibuat:

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” adalah aturan anti-passthrough, dan itulah alasan seluruh perangkat audience ada. Server yang memutar ulang bearer token yang diberikan kepadanya ke API pihak ketiga adalah confused deputy: ia meminjamkan trust-nya sendiri kepada siapa pun yang memanggilnya. Aturan itu melarang reuse, bukan hanya storage.

Membuat itu enforceable membutuhkan empat RFC, masing-masing satu tugas.6 RFC 9728 adalah cara client menemukan authorization server sejak awal: MCP server menyajikan protected-resource-metadata document dan 401 menunjuk ke sana. RFC 8707 adalah parameter resource — client harus mengirim URI kanonis server dalam kedua authorization request dan token request, “terlepas dari apakah authorization servers mendukungnya”, sehingga token yang diterbitkan menamai audience-nya. RFC 9207 menutup loop dari sisi lain: client mencatat issuer sebelum redirect dan membandingkan iss yang dikembalikan sebagai string persis, tanpa normalisasi — tanpa case folding, tanpa penghilangan default-port, tanpa trailing slash. Dan RFC 7591, Dynamic Client Registration, kini deprecated demi Client ID Metadata Documents, “dipertahankan untuk backwards compatibility dengan authorization servers yang tidak mendukungnya”.4

Rangkai itu di kedua server dengan token verifier yang tidak melakukan apa pun selain memeriksa audience. Tangga 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"}

Kedua SDK menyajikan document itu dan keduanya mengarahkan 401 ke sana, yaitu seluruh cerita discovery: client yang belum pernah melihat server kamu belajar di mana harus authenticate dari sebuah penolakan. 403 adalah makhluk berbeda — token-nya baik, scope-nya tidak — dan challenge menyebut apa yang kurang agar client bisa naik tingkat, bukan mulai dari awal.

Dua anak tangga berbeda, dan tidak satu pun perbedaannya ada dalam spesifikasi. TypeScript SDK menolak token dengan tanpa expiry claim; Python mengembalikan 200, karena expires_at optional pada AccessToken miliknya dan None berarti “tidak punya pendapat”. Dan 403 Python membawa error_description="Required scope: incidents:read" tanpa parameter scope yang menurut spesifikasi sebaiknya disertakan server. Verifier bukan tempat menerima default library: audience check harus kamu tulis sendiri dalam bahasa apa pun, begitu juga expiry.

Satu catatan kecil yang jujur dari run yang sama. GET pada endpoint menjawab 404 di wiring Express dan 400 Bad Request: Missing session ID di Python, sementara spesifikasi meminta 405 Method Not Allowed dan “session ID” adalah kosakata yang dihapus revisi ini. Tidak satu pun berbahaya; keduanya adalah bentuk ekosistem di tengah migration.

Bagian terakhir dari shipping adalah di mana kamu publish, dan ada jawaban dengan angka. Dicrawl hari ini, setiap server di registry resmi pada versi terbarunya:7

servers
total (versi terbaru, tidak dihapus)28.170
aktif / deprecated27.853 / 317
ship setidaknya satu installable package13.065
remote saja — URL, tidak ada yang perlu di-install14.696
npm8.275
PyPI3.603
OCI images867
bundle mcpb706
NuGet / Cargo107 / 43

Dua pembacaan, menunjuk ke arah berlawanan. Berdasarkan server yang dipublikasikan, npm unggul 2,3 banding 1 — angka yang dikutip orang ketika mereka bilang ekosistemnya TypeScript. Berdasarkan downloads, Python unggul: selama tiga puluh hari terakhir mcp mendapat 286,7 juta dibanding @modelcontextprotocol/sdk pada 194,7 juta, sebelum menambahkan fastmcp pada 72,1 juta.7 Keduanya Tier 1, schema normatifnya adalah schema.ts, dan tutorial resmi “Build an MCP server” dibuka pada tab Python.1 Separuh mana pun yang ada di kepalamu, separuh lainnya juga benar.

Dan baris yang lebih penting daripada keduanya: lebih dari separuh registry — 14.696 dari 28.170 — tidak punya apa pun untuk di-install. Itu adalah web services. Hitungan transport setuju dari sisi lain: dari 14.290 package entries, 13.787 mendeklarasikan stdio; dari 16.640 remote entries, 15.570 mendeklarasikan Streamable HTTP dan 1.070 masih mendeklarasikan HTTP+SSE yang deprecated. Jadi “MCP server adalah subprocess di laptopmu” menggambarkan minoritas yang menyusut, dan setiap satu dari 14.696 membutuhkan bagian di atas, bukan environment variable.

Tampilkan detail

Sengaja bilingual, dan presedennya.

Ini satu-satunya chapter bilingual dalam course, karena jawaban jujurnya terbelah: registry npm-first dan downloads Python-first, pada saat yang sama, hari ini. Menulis salah satu saja akan menyerahkan separuh pertanyaan dan salah menggambarkan ekosistem saat melakukannya. Ada preseden terbuka — Hugging Face MCP Course mencantumkan di antara prasyaratnya “Experience with at least one programming language (Python or TypeScript examples will be shown)”, dan mengajarkan keduanya.8 Protokol yang seluruh nilainya adalah jumlah implementasi bukan tempat yang baik untuk menjadi monolingual.

Bagian bertanggal: semua di atas yang punya masa berlaku

Tautan ke bagian: Bagian bertanggal: semua di atas yang punya masa berlaku

Dibaca dan diukur pada 7 September 2026, terhadap protocol revision 2026-07-28.

value
@modelcontextprotocol/sdk1.30.0, dipublikasikan 27 Juli 2026; 4.322.438 byte unpacked, 693 file, 17 direct dependencies
revisi terbaru yang diimplementasikan2025-11-25
mcp (PyPI)2.1.1, dipublikasikan 25 Agustus 2026; wheel 357.912-byte, plus mcp-types 2.1.1 pada 69.656 byte
revisi terbaru yang diimplementasikan2026-07-28
SDK tiersTypeScript, Python, C#, Go, Rust di Tier 1; Java, Ruby di Tier 2; Swift, PHP, Kotlin di Tier 3
registry servers28.170
downloads, 30 hari terakhirmcp 286.653.871 · fastmcp 72.097.269 · @modelcontextprotocol/sdk 194.679.333

Satu catatan migration yang bukan angka. Di mcp 2.x, FastMCP diganti nama menjadi MCPServer, dan hampir setiap tutorial online masih dibuka dengan import lama. SDK mengirim module yang tujuan satu-satunya adalah menjelaskan itu, deprecation paling penuh perhatian dalam chapter ini:

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.

Dengan tabel di depanmu, rekomendasinya membosankan, dan itu pertanda baik.

Jika server hidup di dalam web application yang sudah kamu jalankan, tulis dengan TypeScript. Process yang sama, deploy yang sama, request handler yang sama; Streamable HTTP adalah endpoint yang kamu tambahkan di samping yang lain; dan 13,9 MiB serta 145 ms itu gratis karena runtime sudah hidup. Itulah sebagian besar dari 14.696 remote servers.

Jika server membungkus data tooling, tulis dengan Python. Yang kamu ekspos adalah pandas, warehouse client, transform setara satu notebook, dan server dalam bahasa lain akan menjadi subprocess call yang memakai schema. Tujuh ratus milidetik import dalam service yang start sekali bukan biaya; dalam subprocess yang diluncurkan ulang host sepanjang hari, itu biaya.

Dan untuk saat ini, baris revisi mengalahkan keduanya. Jika kamu butuh 2026-07-28 — multi-round-trip requests, resultType, cache hints, server/discover — salah satu dari dua SDK memilikinya hari ini dan yang lain tidak.

Sekarang kamu bisa ship server yang sama dalam bahasa mana pun, mempertahankan pilihannya dengan tabel alih-alih preferensi, menjalankannya di kedua live transports, dan memberinya token yang akan ia tolak.

Yang kamu bangun masih sebuah function: schema, endpoint, sesuatu yang deterministic yang dipanggil model. Satu kelas pengetahuan utuh tidak cocok dengan bentuk itu — bagaimana kami menulis postmortem, field apa yang dibutuhkan laporan insiden kami, urutan kami melakukan sesuatu dan alasannya. Itu prosedur, itu prosa, dan memaksanya masuk ke deskripsi tool adalah cara system prompts tumbuh menjadi dua ribu token yang dibayar pada setiap turn tunggal, terlepas percakapan itu tentang insiden atau bukan.

Chapter 28 adalah jawaban lainnya: folder dengan SKILL.md di dalamnya yang dibaca model alih-alih dipanggil, dimuat dalam tiga level sehingga materi referensi nyaris tidak memakan biaya sampai turn yang membutuhkannya. Ia tidak punya bahasa utama, dan itulah hal pertama yang ia ajarkan.


Semua di sini diukur pada 7 September 2026, di Node 22.22.3 dan Python 3.14.4, terhadap @modelcontextprotocol/sdk 1.30.0 dengan zod 3.25.76 dan mcp 2.1.1, masing-masing di-install ke direktori sekali pakai sendiri. Timing adalah median dari 25 launch, wall clock dari spawn ke baris yang membawa response tools/list; hitungan token adalah o200k_base via tiktoken atas JSON dari tiap definition. Tidak ada API berbayar yang dipanggil: tidak ada di sini yang membutuhkan model.

Kedua server adalah 81 dan 63 baris non-kosong; satu dari tiga tools mereka direproduksi di atas dalam kedua bahasa, dan empat registrasi lainnya hanya berbeda seperti dijelaskan. Kebijakan error-disclosure Python SDK dikutip dari docstring ToolError dan UnexpectedToolError dalam mcp/server/mcpserver/exceptions.py; default pretty-printing adalah pydantic_core.to_json(result, fallback=str, indent=2) dalam mcp/server/mcpserver/resources/types.py dan utilities/func_metadata.py. Konstanta protocol-version adalah LATEST_PROTOCOL_VERSION dalam mcp_types/version.py dan dalam types.js TypeScript SDK, keduanya dibaca dari package terpasang alih-alih dari changelog.

  1. SDKs, modelcontextprotocol.io/docs/sdk, dan Build an MCP server, modelcontextprotocol.io/docs/develop/build-server, keduanya dibaca 7 September 2026. Sumber tabel tier, kalimat “Each SDK provides the same functionality but follows the idioms and best practices of its language”, urutan language-tab tutorial (Python, TypeScript, Java, Kotlin, C#, Ruby, Rust, Go), dan aturan logging yang dikutip tentang print() dan stdout. 2 3 4

  2. stdio transport, .../basic/transports/stdio. Sumber newline framing dan aturan purity stdout. Chapter 26 membaca halaman ini secara penuh; ia dikutip di sini untuk baris yang dilanggar server rusak.

  3. MCP Inspector, modelcontextprotocol.io/docs/2026-07-28/tools/inspector, dibaca 7 September 2026. Satu package, tiga clients di balik satu binary — web, --cli, dan --tui — berbagi satu core, satu set transports, dan satu OAuth state di disk. CLI menghasilkan trace catalogue di sini.

  4. Authorization, modelcontextprotocol.io/specification/2026-07-28/basic/authorization, dibaca 7 September 2026. Sumber peran resource-server; empat klausul penanganan token yang dikutip penuh; kewajiban server mengimplementasikan RFC 9728 dan client memakainya untuk discovery; aturan parameter resource dan definisi canonical-URI; tabel issuer-validation; deprecation Dynamic Client Registration; tabel 401/403/400 dan challenge insufficient_scope; serta pengecualian 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, dan Transports overview, .../basic/transports. Sumber aturan single-endpoint POST, kewajiban dual Accept, header MCP-Protocol-Version dan aturan must-match-the-body, header Mcp-Method dan Mcp-Name yang dijelaskan sebagai “REQUIRED for compliance”, penghapusan GET stream, sessions, dan Last-Event-ID, panduan 405, validasi wajib Origin, dan klasifikasi transport HTTP+SSE 2024-11-05 sebagai Deprecated di bawah SEP-2596. 2 3 4

  6. Empat yang dijadikan tumpuan spesifikasi, dengan draft yang diprofilkannya: The OAuth 2.1 Authorization Framework, draft-ietf-oauth-v2-1-13. Campbell, B., Bradley, J. dan Tschofenig, H., Resource Indicators for OAuth 2.0, RFC 8707, Februari 2020 — parameter resource dan audience yang diikatnya. Jones, M.B., Hunt, P. dan Parecki, A., OAuth 2.0 Protected Resource Metadata, RFC 9728, April 2025 — document yang ditunjuk 401. Meyer zu Selhausen, K. dan Fett, D., OAuth 2.0 Authorization Server Issuer Identification, RFC 9207, Maret 2022 — parameter iss dan perbandingan exact-string. Richer, J. (ed.) dkk., OAuth 2.0 Dynamic Client Registration Protocol, RFC 7591, Juli 2015, deprecated untuk penggunaan ini. Dan Jones, M. dan Hardt, D., The OAuth 2.0 Authorization Framework: Bearer Token Usage, RFC 6750, Oktober 2012, section 3, untuk bentuk challenge WWW-Authenticate di atas.

  7. Registry MCP resmi, registry.modelcontextprotocol.io/v0/servers, dicrawl 7 September 2026 dengan version=latest: 282 halaman, 28.170 servers, dihitung oleh registryType atas nama server distinct. Angka download: api.npmjs.org/downloads/point/last-month untuk @modelcontextprotocol/sdk (194.679.333 untuk 8 Agustus – 6 September 2026) dan pypistats.org/api/packages/<name>/recent untuk mcp dan fastmcp, keduanya dibaca pada hari yang sama. Ukuran package berasal dari dokumen registry npm dan PyPI JSON API. 2

  8. MCP Course, Hugging Face, huggingface.co/learn/mcp-course, unit 0, dibaca 7 September 2026: di antara prasyaratnya, “Experience with at least one programming language (Python or TypeScript examples will be shown)”.

Siap membiarkan LIA yang memilih?

Berkarya dengan semua model AI dalam satu tempat — mulai gratis hari ini.