İçeriğe geç
27/3030 bölümden 27. bölüm

Bir MCP Server Yayına Almak: TypeScript ve Python, Ölçülmüş

Aynı server iki kez yazıldı — üç araç, bir kaynak, bir prompt — sonra tartıldı. 94 pakete karşı 28, 145 ms cold start'a karşı 709.

Bu sayfada

İşte tüm dil tartışması, tek kelime kurulmadan önce ölçülmüş hâliyle.

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

İlk iki satır herkesin istediği karşılaştırma. Üçüncü satır, ilk satırdaki aynı TypeScript server'ın gerçekten dağıtılacağı şekilde başlatılmış hâli — ve Python'dan üç milisaniye uzağa düşüyor.

26. Bölüm, Model Context Protocol'ü ham JSON-RPC ile kendi spesifikasyonuna karşı okudu, çünkü ham JSON-RPC'nin dili yoktur. Bu bölümde iki dil var ve tartışmanın ağırlığı burada: aynı server, iki kez yazıldı. Üç araç, bir kaynak, bir prompt, iki SDK, iki tarafta da kestirme yok. Sonra transports, inspector, 401 ve kimsenin yayımlamadığı sayılar.

Server ve içinde neden bu beş şeyin olduğu

Bölüme bağlantı: Server ve içinde neden bu beş şeyin olduğu

Bir olay kaydı. Üç araç, çünkü 18. Bölüm'ün okuma ve yazma ayrımı görünür olmalı: search_incidents okur, open_incident yazar ve bir handle döndürür, resolve_incident o handle'ı alır ve kapatır. Bir kaynak, incidents://open, çünkü mevcut listeyi okumak uygulamanın bağladığı bir şeydir. Bir prompt, postmortem, çünkü «bunu yazıya dök» bir kişinin slash command'ıdır. Bu, 26. Bölüm'ün kontrol hiyerarşisinin — model, uygulama, kişi — beş registration'a dönüştürülmüş hâlidir.

Handle göründüğünden daha önemlidir. 26. Bölüm, durumunu module-level array'de tutarak bir oyuncak takvimi bozmuştu: protokolde session yoktur, bu yüzden bir oluşturma aracı opak bir tanımlayıcı döndürür ve sonraki her çağrı bunu sıradan bir argüman olarak alır. İki dosyanın hiçbirinde çağıranın onu açan process olduğu varsayılmaz.

İşte aynı araç, iki dilde yan yana register edilmiş hâliyle:

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

Önce aynı olanı okuyun, çünkü bulgu bu. İkisi de bir ad, bir açıklama, açıklanmış iki string argümanı ve üç annotation bildiriyor; ikisi de tek function; hiçbiri JSON-RPC'den, framing'den, stdout'dan veya bir protokol sürümünden söz etmiyor. İki SDK aynı şekle yakınsamış; «Tier 1» denen şeyin anlamı da bu olmalı.1

İki fark gerçek ve ikisi de sonra geri dönüyor. TypeScript argümanları bir schema library ile tarif ediyor — burada Zod — ve schema yazdığınız bir değer. Python ise onları function'ın kendi type hint'leriyle tarif ediyor ve import zamanında okuyor; bu yüzden TypeScript dosyasının hiç söylemediği şeyleri function hakkında biliyor. Bir de hata yolu: TypeScript isError içeren bir tool sonucu döndürüyor, Python raise ediyor. Bunu aklınızda tutun.

Diğer dört registration yapısal olarak hiçbir şeyde farklı değil. Resource server.registerResource("open-incidents", "incidents://open", …) ile @server.resource("incidents://open", …) karşı karşıya; prompt registerPrompt ile @server.prompt karşı karşıya. Her dosyanın son satırı transport: await server.connect(new StdioServerTransport()) ile server.run().

Tam dosyalar: 81 boş olmayan satır ve 3.060 byte TypeScript; 63 satır ve 2.555 byte Python. Bunu hak ettiği tuzla alın — satır sayıları bir dili ölçtüğü kadar formatter'ı da ölçer; aşağıdaki başlık tablosunda iki sayının da olmamasının nedeni bu.

Dilin görünmez olduğunun kanıtı, on bir satırda iki kez çalıştırılan tek client'tır:

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

Sırayla her server'a yöneltin. Gerçek çıktı, kısaltılmış:

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

Aynı tools, aynı sıra, aynı handle. Bir TypeScript client server'ın neyle yazıldığını anlayamaz ve asla sormaz. Bir protokol vaadinin tutması tam olarak budur.

Şimdi ikinci sonuçtaki whitespace'e bakın, çünkü kozmetik değil: Python SDK payload'ları pydantic_core.to_json(result, fallback=str, indent=2) ile serialise ediyor. Listede iki olay varken resource read'de TypeScript gövdesi 136 karakter ve 37 o200k_base token; Python gövdesi 185 ve 62. Aynı satırlar için yüzde altmış sekiz daha fazla token; resource'u bir prompt içine okuyan kişi bunu her seferinde öder.

Katalogda da aynı hikâye var, ama nedeni daha büyük. İki server, aynı üç araç, tools/list key key tartıldı:

keyTypeScriptPython
name2121
description4646
annotations4646
inputSchema211192
outputSchema187
execution27
toplam342480

Python'ın input schema'ları daha ucuz — TypeScript'in Zod köprüsü her birine bir $schema ve bir additionalProperties damgalıyor. 138-token farkın tamamı kimsenin yazmadığı bir output schema. resolve_incident, -> Incident olarak annotation'lı, bu yüzden SDK return type için bir JSON Schema türetip göndermiş. Gerçekten faydalı — client'ın structuredContent'ı validate etmesini sağlayan şey bu — ve bir type hint yüzünden context window'unuza gelen 187 token. 24. Bölüm'ün tanımların önemli malzemeyi sıkıştırmasıyla ilgili kuralı, varlığından haberiniz olmayan schema'lar için de geçerli.

Yukarıdaki iki hata yolu bir style tercihi değil. Her server'a gerçek bir integration'ın başarısız olduğu gibi başarısız olan bir araç verin ve modele ne ulaştığını okuyun.

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, modelin context'ine bir internal address, bir port, bir database adı ve bir service account koydu. Python SDK bunların hiçbirini oraya koymadı; traceback stderr'e gitti ve server'da kaldı.

İkisi de bug değil. İkisi de karar ve Python'ınki kendi docstring'inde yazıyor: ToolError, «öngördüğünüz bir failure»dır ve mesajı «modelin okuması için content içinde» döndürülür; başka her şey «crash olarak ele alınır: model yalnızca Error executing tool <name> görür ve server traceback'i ERROR seviyesinde loglar». Crash durumu için class gerisini yüksek sesle söyler — «orijinalden hiçbir şey client'a ulaşmaz».

İki davranış da zamanın yarısında yanlıştır. 18. Bölüm, bir validation error'ın modelin okuyup düzeltebileceği bir tool sonucu olarak dönmesi gerektiğini savundu, çünkü çoğu integration'da en yüksek kaldıraçlı satır budur; Python tarafında bu, açıkça ToolError raise etmeyi gerektirir ve çıplak ValueError işe yarar cümleyi çöpe atar. 30. Bölüm'ün argümanı ters yönde ilerler: bir aracın döndürdüğü her şey, sonraki bir prompt injection'ın geri okumaya çalışabileceği bir context'e iner ve gözden geçirilmemiş exception string'i sisteminizdeki en az denetlenmiş metindir.

İkisinden de sağ çıkan kural: her araç için bir failure'ın ne söylemesine izin verildiğine karar verin ve o string'i kendiniz yazın. İki dilde de bir exception'ın default metninin karar vermesine asla izin vermeyin.

Bilerek bozun: standard output'ta tek satır

Bölüme bağlantı: Bilerek bozun: standard output'ta tek satır

Resmî tutorial kuralı hiç yumuşatmadan söylüyor: «STDIO tabanlı server'lar için: stdout'a asla yazmayın. stdout'a yazmak JSON-RPC mesajlarını bozar ve server'ınızı kırar. print() function default olarak stdout'a yazar, bu yüzden onu STDIO server'dan tamamen uzak tutun.»1 26. Bölüm normatif sürümü alıntıladı — bir server «geçerli bir MCP mesajı olmayan hiçbir şeyi stdout'ına yazmamalıdır».2

Her server'a bir satır ekleyin ve raw stream'i okuyun:

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

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

Python olanı daha kötü ve nedeni MCP değil. stdout'ı terminal yerine pipe olan bir process block-buffered stream alır; bu yüzden başıboş satır buffer ne zaman isterse o zaman flush edilir — burada exit anında, önce yazıldığı bir response'tan sonra. Bozulma bug'ın olduğu yerde görünmez. flush=True ekleyin veya flush eden bir library kullanın; yer değiştirir.

Sonra bunun neden ship edildiğini açıklayan kısım. Bozuk server'ı üç client'a verin:

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

Yedi satırlık parser hemen ölür. Resmî client ve Inspector omuz silker — satırı atlar ve devam eder. Yalnızca kimsenin kullanmadığı client'ları bozan bir kural, production'a sağlam ulaşan bir kuraldır; bu yüzden burada, bir müşterinin log'unda değil, bilerek bozmaya değer.

Inspector'ın CLI modu unutulan yarıdır: npx @modelcontextprotocol/inspector --cli <command> --method tools/list bir katalog basar ve çıkar; bu, onu browser UI'ın olmadığı kadar scriptable yapar.3

İki SDK da temiz kuruldu, kendi dizinlerine, hiçbir şey paylaşılmadan:

TypeScriptPython
package@modelcontextprotocol/sdk 1.30.0 + zod 3.25.76mcp 2.1.1
implement edilen en son protocol revision2025-11-252026-07-28
kurulan transitive packages9428
kurulu boyut13.9 MiB44.3 MiB
diskteki dosyalar3.3862.018
stdio sunmak için yüklenen third-party packages94'ten 828'den 18
boş interpreter başlangıcı, median19.4 ms11.1 ms
spawn → tools/list yanıtlandı, 25 çalıştırmanın median'ı144.5 ms709.4 ms
tools/list kataloğu, o200k_base token342480

Her satır farklı bir yönde şaşırtıyor; karşılaştırmanın varsayılmak yerine çalıştırılmaya değer olmasının nedeni bu.

TypeScript üç kattan fazla paket ve üçte birinden az byte kuruyor. 94 dependency, npm ekosisteminin kendisi gibi davranması — fast-deep-equal, es-errors, dunder-proto. Python'ın 28'i daha az ve devasa: cryptography, pydantic-core ve uvicorn compiled artefact'ler. Endişelenmeniz gereken şeyin dependency sayısı olduğunu düşünüyorsanız, bu satır karşı örnektir.

Python'ın interpreter'ı Node'dan daha hızlı başlıyor ve fark yakın bile değil — boş programda 19.4 ms'ye karşı 11.1 ms. Yani cold-start satırındaki 565 ms dil değil. SDK ve yüklenen paketler satırı nedenini söylüyor:

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

Tek I/O'su pipe olan bir server, ilk satırını okumadan önce bir ASGI web server, bir HTTP client ve bir TLS library import ediyor. TypeScript SDK da Express, Hono, jose ve eventsource gönderiyor — package boundary onları server/stdio.js import'unun dışında tuttuğu için diskte okunmadan duruyorlar. Python package tek import graph; bu yüzden import mcp hepsidir: python -X importtime, import mcp.server.mcpserver'e 727 ms atfediyor — import profiler altında ölçülen bir rakam olduğu için, unprofiled çalışmanın spawn'dan yanıta aldığı 709 ms'nin üstünde çıkıyor — ve bunun 269 ms'si yalnızca mcp.types subtree'sine gidiyor — wire types Pydantic modelleri, her protokol mesajı ve revision için bir class; onları oluşturmak import sırasında yapılan iştir. Bu bir design trade, özensizlik değil — eager imports, Python SDK'nın bir sonraki satırda ikinci bir install olmadan size run(transport="streamable-http") verebilmesinin nedeni.

Sonra açılış bloğunun son satırı argümanı geri alıyor. TypeScript server'ı doğru paketleyin — bir bin entry, bir shebang, npm link, indirilecek hiçbir şey yok — ve yayımlanmış bir stdio server'ın gerçekten başlatıldığı gibi npx üzerinden --no-install ile çalıştırın:

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 her başlangıçta 568 ms tutuyor — TypeScript SDK import'unun tamamının dört buçuk katı — ve her launch'ta ödeniyor, çünkü bir MCP host stdio server'ı o komutu çalıştırarak başlatıyor. Bu yüzden «TypeScript beş kat hızlı başlıyor»un dürüst biçimi şu: normal şekilde dağıtana kadar, evet. Aynı çekince muhtemelen uvx için de geçerli; bu makinede uv kurulu değildi, bu yüzden o satır yok. Ölçülmeyen hiçbir şey tabloya girmez.

  1. Bölüm stdio'nun framing'ini ele aldı. İki şeyi buraya bıraktı.

Birincisi: bir server'ı npx veya uvx ile çalıştırmak stdio transport'un kendisidir. Ayrı bir «package mode» yok. Bir host'un configuration'ı bir command ve arguments adlandırır; host onu spawn eder ve pipe'lar üzerinden konuşur. Bu yüzden yerelde «bunu nasıl dağıtırım» ve «hangi transport'u konuşuyor» aynı sorudur ve launcher'ın maliyeti shipping hakkında bir bölüme aittir.

İkincisi: stdio'nun authorization bölümü hiç yoktur ve specification bunu tek satırda söyler — stdio kullanan implementations «bu specification'ı takip etmemeli, bunun yerine credentials'ı environment'tan almalıdır».4 Security model'i işletim sistemininkidir; sınırı da öyle: local subprocess tam olarak bir makineye ve bir kullanıcıya hizmet eder.

Diğer canlı transport Streamable HTTP'dir: POST kabul eden tek endpoint, JSON-RPC mesajı başına bir HTTP request ve hem application/json hem text/event-stream listelemesi gereken bir Accept header, çünkü server her request için ikisinden hangisiyle yanıtlayacağını seçer.5 14. Bölüm bu event stream'i elle parse etmişti, bu yüzden wire format'ta yeni bir şey yok — yalnızca onu saran şey yeni. Güncel revision'ın üç yükümlülüğünü kaçırmak kolay ve üçü de test edilebilir:

Version header body ile aynı fikirde olmalı

Bölüme bağlantı: Version header body ile aynı fikirde olmalı

Her POST MCP-Protocol-Version taşır ve değeri request'in kendi _meta içindeki protocolVersion ile eşleşmelidir. Mismatch, omuz silkmek değil, header-mismatch error'ıyla 400'dir.5

Mcp-Method her request'te method'u yansıtır; Mcp-Name, tools/call, resources/read ve prompts/get üzerinde params.name veya params.uri'i yansıtır. Bir proxy body parse etmeden route edebilsin diye vardırlar.5

Eski şekiller gitti ve ret yanıtıyla cevap verir

Bölüme bağlantı: Eski şekiller gitti ve ret yanıtıyla cevap verir

GET stream, Mcp-Session-Id ve Last-Event-ID resumption kaldırıldı. Yalnızca bu revision'ı konuşan bir server, GET veya DELETE'e 405 Method Not Allowed yanıtlamalı, session header'ını yenisini mint etmeden görmezden gelmeli ve Last-Event-ID'i görmezden gelmelidir.5

Şimdi tüm bölümü yeniden çerçeveleyen ölçüm. Her server'a HTTP üzerinden current-revision request gönderin.

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

Constants davranışla aynı fikirde: Python SDK'nın LATEST_PROTOCOL_VERSION'i 2026-07-28 okuyor, TypeScript SDK'nınki 2025-11-25 okuyor. Yukarıdaki adımdan header-mismatch request'i gönderin; Python server 400 ile, -32020 error'ıyla ve «mcp-protocol-version header does not match the request envelope's protocol version» mesajıyla yanıtlıyor; TypeScript SDK'da böyle code yok, çünkü onu tanımlayan revision'ı implement etmiyor.

İkisini de Tier 1 olarak listeleyen sayfa ayrıca «Each SDK provides the same functionality» diyor.1 Aşağıdaki tarihte, current revision için, bu cümle niyet beyanı. Kurmak üzere olduğunuz SDK'da LATEST_PROTOCOL_VERSION'i kontrol edin; tek satırdır ve bu bölümde bir yıl sonra hâlâ önem taşıyacak tek claim budur.

Bir server'ı laptop'ınızdan çıkarın ve bir yabancının client'ı bir token ile gelir. Bu, 26. Bölüm'ün yalnız bıraktığı yarı ve multi-user bir product'ın atlayamayacağı yarıdır.

Specification, MCP server'ı bir OAuth 2.1 rolüne koyar ve adını verir: protected MCP server bir resource server'dır, client bir OAuth client'tır ve authorization server başkasının problemidir.4 Bu rolden dört mandatory clause çıkar; hatanın paraphrase edilirken yapılmasından dolayı tamamını alıntılıyorum:

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 kuralıdır ve tüm audience aygıtının varlık nedeni budur. Kendisine verilen bearer token'ı third-party API'da yeniden oynatan bir server confused deputy'dir: kendi güvenini onu çağırana ödünç verir. Kural yalnızca storage'ı değil, reuse'u yasaklar.

Bunu enforce edilebilir yapmak dört RFC ister, her birinin işi ayrı.6 RFC 9728, client'ın authorization server'ı en başta nasıl bulduğudur: MCP server protected-resource-metadata document sunar ve 401 ona işaret eder. RFC 8707, resource parameter'dır — client, authorization server'lar desteklese de desteklemese de, authorization request'te ve token request'te server'ın canonical URI'sini göndermelidir; böylece verilen token audience'ını adlandırır. RFC 9207 döngüyü diğer taraftan kapatır: client redirect etmeden önce issuer'ı kaydeder ve dönen iss'yi exact string olarak karşılaştırır; normalisation yok — case folding yok, default port elision yok, trailing slash yok. Ve RFC 7591, Dynamic Client Registration, artık Client ID Metadata Documents lehine deprecated; onları desteklemeyen authorization server'larla backwards compatibility için «retained».4

Bunu iki server'a da yalnızca audience kontrol eden bir token verifier ile bağlayın. TypeScript merdiveni:

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

İki SDK da o document'ı sunar ve ikisi de bir 401'i ona yöneltir; discovery hikâyesinin tamamı budur: server'ınızı hiç görmemiş bir client, nerede authenticate edeceğini bir refusal'dan öğrenir. 403 başka bir hayvandır — token iyi, scope değildir — ve challenge neyin eksik olduğunu adlandırır, böylece client baştan başlamak yerine step up edebilir.

İki basamak farklıdır ve iki fark da specification'da değildir. TypeScript SDK expiry claim olmayan token'ı reddeder; Python olan 200 döndürür, çünkü AccessToken üzerindeki expires_at optional'dır ve None «fikrim yok» demektir. Ayrıca Python 403, specification'ın server'ların eklemesi gerektiğini söylediği scope parameter olmadan error_description="Required scope: incidents:read" taşır. Verifier, library default'unu kabul edilecek yer değildir: audience check iki dilde de sizin yazacağınız şeydir, expiry de öyle.

Aynı run'dan dürüst bir pürüz. Endpoint'e GET, Express wiring üzerinde 404, Python olanda 400 Bad Request: Missing session ID döndürdü; specification'ın istediği 405 Method Not Allowed ve «session ID» de bu revision'ın kaldırdığı vocabulary. İkisi de tehlikeli değil; ikisi de mid-migration bir ekosistemin şekli.

Shipping'in son parçası nerede publish ettiğinizdir ve bunun sayılı bir cevabı var. Bugün crawl edildi, resmî registry'deki her server latest version'ıyla:7

server'lar
toplam (latest version, silinmemiş)28.170
active / deprecated27.853 / 317
en az bir installable package ship ediyor13.065
yalnızca remote — bir URL, install edilecek hiçbir şey yok14.696
npm8.275
PyPI3.603
OCI images867
mcpb bundles706
NuGet / Cargo107 / 43

İki okuma, ters yönleri gösteriyor. Yayımlanmış server sayısında npm 2.3'e 1 önde — insanlar ekosistemin TypeScript olduğunu söylerken alıntıladıkları sayı bu. Downloads tarafında Python önde: son otuz günde mcp, @modelcontextprotocol/sdk'nin 194.7 milyonuna karşı 286.7 milyon aldı; fastmcp'nin 72.1 milyonu eklenmeden önce.7 İkisi de Tier 1, normative schema bir schema.ts ve resmî «Build an MCP server» tutorial Python tab'ında açılıyor.1 Kafanızda bunun hangi yarısı vardıysa, diğer yarısı da doğru.

Ve ikisinden de daha önemli satır: registry'nin yarısından fazlasında — 28.170'in 14.696'sında — install edilecek hiçbir şey yok. Bunlar web service'ler. Transport sayımları diğer taraftan aynı fikirde: 14.290 package entry'den 13.787'si stdio bildiriyor; 16.640 remote entry'den 15.570'i Streamable HTTP ve 1.070'i hâlâ deprecated HTTP+SSE bildiriyor. Yani «bir MCP server laptop'ınızdaki subprocess'tir» giderek küçülen bir azınlığı tarif ediyor ve 14.696'nın her biri yukarıdaki bölüme ihtiyaç duyuyor, environment variable'a değil.

Ayrıntıları göster

Bilerek iki dilli ve bunun emsali.

Bu course'taki tek iki dilli bölüm bu, çünkü dürüst cevap ikiye bölünüyor: registry npm-first ve downloads Python-first; aynı anda, bugün. İkisinden birini yazmak sorunun yarısını ele verir ve bunu yaparken ekosistemi yanlış tarif ederdi. Açıkta emsal var — Hugging Face MCP Course prerequisites arasında «Experience with at least one programming language (Python or TypeScript examples will be shown)» listeliyor ve ikisini de öğretiyor.8 Tüm değeri implementations sayısından gelen bir protokol, monolingual olmak için kötü bir yerdir.

Tarihli bölüm: yukarıda raf ömrü olan her şey

Bölüme bağlantı: Tarihli bölüm: yukarıda raf ömrü olan her şey

7 Eylül 2026 tarihinde, protocol revision 2026-07-28'e karşı okundu ve ölçüldü.

değer
@modelcontextprotocol/sdk1.30.0, 27 Temmuz 2026'da yayımlandı; unpacked 4.322.438 byte, 693 dosya, 17 direct dependencies
implement ettiği latest revision2025-11-25
mcp (PyPI)2.1.1, 25 Ağustos 2026'da yayımlandı; 357.912-byte wheel, artı 69.656 byte ile mcp-types 2.1.1
implement ettiği latest revision2026-07-28
SDK tiersTypeScript, Python, C#, Go, Rust Tier 1; Java, Ruby Tier 2; Swift, PHP, Kotlin Tier 3
registry server'ları28.170
downloads, son 30 günmcp 286.653.871 · fastmcp 72.097.269 · @modelcontextprotocol/sdk 194.679.333

Sayı olmayan bir migration notu. mcp 2.x'te FastMCP, MCPServer olarak yeniden adlandırıldı ve online tutorial'ların neredeyse hepsi hâlâ eski import ile açılıyor. SDK, tek amacı bunu açıklamak olan bir module gönderiyor; bu bölümdeki en düşünceli deprecation da bu:

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.

Tablo önünüzdeyken öneri sıkıcı; bu iyiye işaret.

Server zaten çalıştırdığınız bir web application'ın içinde yaşıyorsa TypeScript ile yazın. Aynı process, aynı deploy, aynı request handler; Streamable HTTP diğerlerinin yanına eklediğiniz bir endpoint'tir; 13.9 MiB ve 145 ms bedavadır, çünkü runtime zaten ayaktadır. 14.696 remote server'ın çoğu budur.

Server data tooling'i sarıyorsa Python ile yazın. Dışarı açtığınız şey pandas, bir warehouse client, bir notebook dolusu transform ve başka dildeki bir server schema giymiş bir subprocess call olurdu. Bir kez başlayan service'te 700 ms import maliyet değildir; host'un gün boyu yeniden launch ettiği subprocess'te maliyettir.

Ve şimdilik revision satırı ikisini de geçersiz kılar. 2026-07-28'e ihtiyacınız varsa — multi-round-trip requests, resultType, cache hints, server/discover — iki SDK'dan birinde bugün var, diğerinde yok.

Artık aynı server'ı iki dilde de ship edebilir, seçimi preference yerine tabloyla savunabilir, iki live transport üzerinden çalıştırabilir ve reddedeceği bir token verebilirsiniz.

İnşa ettiğiniz şey hâlâ bir function: bir schema, bir endpoint, modelin invoke ettiği deterministic bir şey. Bütün bir bilgi sınıfı bu şekle sığmaz — bizim postmortem'i nasıl yazdığımız, incident report'larımızın hangi field'lara ihtiyaç duyduğu, işleri hangi sırayla ve neden yaptığımız. Bu procedure'dür, prose'dur ve onu bir tool description'a zorlamak, system prompts'un konuşma incident'larla ilgili olsun olmasın her turn'de ödenen iki bin token'a büyümesinin yoludur.

28. Bölüm diğer cevaptır: içinde modelin çağırmak yerine okuduğu bir SKILL.md bulunan bir folder; reference material, ihtiyaç duyulduğu turn'e kadar neredeyse hiçbir maliyet çıkarmasın diye üç seviyede loaded. Ana dili yoktur ve öğrettiği ilk şey budur.


Buradaki her şey 7 Eylül 2026'da, Node 22.22.3 ve Python 3.14.4 üzerinde, zod 3.25.76 ile @modelcontextprotocol/sdk 1.30.0 ve mcp 2.1.1'e karşı ölçüldü; her biri kendi tek kullanımlık dizinine kuruldu. Timings 25 launch'ın median'ı; wall clock spawn'den tools/list response'u taşıyan satıra kadar; token counts her definition'ın JSON'u üzerinde tiktoken aracılığıyla o200k_base. Paid API çağrılmadı: burada hiçbir şeyin modele ihtiyacı yok.

İki server 81 ve 63 boş olmayan satır; üç araçlarından biri yukarıda iki dilde de yeniden üretildi ve diğer dört registration yalnızca tarif edildiği gibi farklılaşıyor. Python SDK'nın error-disclosure policy'si, mcp/server/mcpserver/exceptions.py içindeki ToolError ve UnexpectedToolError docstring'lerinden alıntılandı; pretty-printing default'u mcp/server/mcpserver/resources/types.py ve utilities/func_metadata.py içindeki pydantic_core.to_json(result, fallback=str, indent=2). Protocol-version constants, mcp_types/version.py içindeki LATEST_PROTOCOL_VERSION ve TypeScript SDK'nın types.js dosyasında; ikisi de changelog'dan değil, installed packages'tan okundu.

  1. SDKs, modelcontextprotocol.io/docs/sdk, ve Build an MCP server, modelcontextprotocol.io/docs/develop/build-server, ikisi de 7 Eylül 2026'da okundu. Tier table'ın, «Each SDK provides the same functionality but follows the idioms and best practices of its language» cümlesinin, tutorial'ın language-tab order'ının (Python, TypeScript, Java, Kotlin, C#, Ruby, Rust, Go) ve print() ile stdout hakkında alıntılanan logging rule'un kaynağı. 2 3 4

  2. stdio transport, .../basic/transports/stdio. Newline framing'in ve stdout purity rule'un kaynağı. 26. Bölüm bu sayfayı tam okur; burada bozuk server'ın ihlal ettiği satır için cited.

  3. MCP Inspector, modelcontextprotocol.io/docs/2026-07-28/tools/inspector, 7 Eylül 2026'da okundu. Tek package, tek binary arkasında üç client — web, --cli ve --tui — tek core, tek transport seti ve diskte tek OAuth state paylaşıyor. Buradaki catalogue traces CLI tarafından üretildi.

  4. Authorization, modelcontextprotocol.io/specification/2026-07-28/basic/authorization, 7 Eylül 2026'da okundu. Resource-server rolünün; tamamı alıntılanan dört token-handling clause'un; server'ların RFC 9728 implement etmesi ve client'ların discovery için kullanması requirement'ının; resource parameter kurallarının ve canonical-URI definition'ın; issuer-validation tablosunun; Dynamic Client Registration'ın deprecation'ının; 401/403/400 tablosunun ve insufficient_scope challenge'ın; ayrıca stdio exemption'ın kaynağı: «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, ve Transports overview, .../basic/transports. Single-endpoint POST kuralının, dual Accept requirement'ın, MCP-Protocol-Version header'ın ve body ile eşleşme zorunluluğunun, compliance için «REQUIRED» olarak tarif edilen Mcp-Method ve Mcp-Name header'larının, GET stream'in, sessions'ın ve Last-Event-ID'ün kaldırılmasının, 405 guidance'ın, mandatory Origin validation'ın ve 2024-11-05 HTTP+SSE transport'un SEP-2596 altında Deprecated olarak sınıflandırılmasının kaynağı. 2 3 4

  6. Specification'ın yaslandığı dört RFC ve profile ettiği draft: The OAuth 2.1 Authorization Framework, draft-ietf-oauth-v2-1-13. Campbell, B., Bradley, J. ve Tschofenig, H., Resource Indicators for OAuth 2.0, RFC 8707, Şubat 2020 — resource parameter ve bağladığı audience. Jones, M.B., Hunt, P. ve Parecki, A., OAuth 2.0 Protected Resource Metadata, RFC 9728, Nisan 2025 — 401'ün işaret ettiği document. Meyer zu Selhausen, K. ve Fett, D., OAuth 2.0 Authorization Server Issuer Identification, RFC 9207, Mart 2022 — iss parameter ve exact-string comparison. Richer, J. (ed.) ve diğerleri, OAuth 2.0 Dynamic Client Registration Protocol, RFC 7591, Temmuz 2015, bu kullanım için deprecated. Ve Jones, M. ve Hardt, D., The OAuth 2.0 Authorization Framework: Bearer Token Usage, RFC 6750, Ekim 2012, section 3, yukarıdaki WWW-Authenticate challenge shape için.

  7. Resmî MCP registry, registry.modelcontextprotocol.io/v0/servers, 7 Eylül 2026'da version=latest ile crawl edildi: 282 page, 28.170 server, distinct server names üzerinde registryType ile tallied. Download figures: @modelcontextprotocol/sdk için api.npmjs.org/downloads/point/last-month (8 Ağustos – 6 Eylül 2026 için 194.679.333) ve mcp ile fastmcp için pypistats.org/api/packages/<name>/recent, hepsi aynı gün okundu. Package sizes npm registry document'tan ve PyPI JSON API'den geliyor. 2

  8. MCP Course, Hugging Face, huggingface.co/learn/mcp-course, unit 0, 7 Eylül 2026'da okundu: prerequisites arasında «Experience with at least one programming language (Python or TypeScript examples will be shown)».


Hazırlayan

David Vicente Campos

NeuraLIA Labs kurucusu ve MyRealFood kurucu ortağı

León Üniversitesinden mezun bir bilgisayar mühendisiyim. MyRealFood’un kurucu ortaklarındanım; orada CTO olarak milyonlarca insanın daha iyi beslenmek için kullandığı uygulamayı geliştirdim. Ayrıca NeuraLIA Labs’i kurdum ve burada AI ürünleri geliştiriyorum. Bu sitede yol boyunca anlamak zorunda kaldığım şeyleri, keşke biri bana böyle anlatsaydı dediğim şekilde yazıyorum.

Yazar hakkında daha fazla

Yayımlayan: NeuraLIA Labs.

Yeni yazılar gelen kutuna gelsin

AI haberleri, rehberler ve ürün güncellemeleri — zamanına değecek bir şey yayımladığımızda kısa bir e-posta.

Mesajlaşmayı mı tercih ediyorsun? Aynı yazılar, burada:WhatsApp topluluğu (yeni sekmede açılır)Telegram kanalı (yeni sekmede açılır)

Kurs dizini

Abstract software decision engine with branching paths, probability nodes, and glowing gates.
jev10 dk okuma

Jev AI modeli düzyazı için değil, kararlar için tasarlandı

TypeSafe AI’ın Jev’i dikkat çekiyor çünkü yazılım zekâsını bir olasılık problemi olarak ele alıyor: doğru dalı seç, güven düzeyini ekle ve kodun bir karara ihtiyacı varken bir LLM’ye metin yazdırmak için ödeme yapma.

Abstract agent runtime sorting documents, memory blocks and pointer nodes inside a bounded context frame.
context-engineering10 dk okuma

Uzun süreli AI ajanları için bağlam mühendisliği

Uzun süre çalışan ajanlar yalnızca pencere küçük olduğu için başarısız olmaz. Dosyalar, araç çıktıları ve bayatlamış geçmiş, ajanın tamamlaması gereken görevi arka plana ittiğinde başarısız olurlar.

Seçimi LIA'ya bırakmaya hazır mısın?

Tüm yapay zeka modelleriyle tek yerde üret — bugün ücretsiz başla.