MCP stateless oluyor ve sürekli çarptığım sorunu çözüyor
2026-07-28 MCP revizyonu protokol seviyesindeki oturumları ve initialize el sıkışmasını kaldırıyor. State artık transport'un içinde saklanmıyor; modelin görebildiği, açık handle'lara taşınıyor. Ne değişti, ne bozuluyor ve bugün ne yapardım, anlatıyorum.
Bir süredir Model Context Protocol'de asıl merak edilen soru bir aracın ne yapabildiği değildi.
State'in nerede yaşadığıydı. MCP hayata stateful, çift yönlü bir protokol olarak başladı:
istemci ve sunucu bir initialize el sıkışması yapıyor, sunucu bir Mcp-Session-Id
döndürüyor ve o andan itibaren iki taraf da bağlantının arkasında bir oturum olduğunu
varsayıyordu. Her şey - liste sonuçları, abonelikler, sunucu tarafından başlatılan istekler -
o oturumun orada durmasına yaslanıyordu.
Bu, bir dizüstünde stdio üzerinden temiz bir model. Ama sunucuyu bir load balancer arkasına koyduğun an baş ağrısına dönüşüyor. Tek bir sürece iğnelenmiş bir oturum, her takip isteğinin aynı instance'a düşmesi gerektiği anlamına gelir; bu da sticky routing demek, ya da paylaşımlı bir oturum deposu, ya da ikisi birden. Yeterince backend sistemi kurdum ve biliyorum ki "bu istek onu başlatan kutuya geri dönmek zorunda" cümlesi, yatay ölçeklenme yeteneğini sessizce öldüren cümledir. Sırf bir araç sunucusu çalıştırmak için, hiç istemediğin bir dağıtık sistemler problemini çözer bulursun kendini.
Bu hafta yayınlanan 2026-07-28 revizyonu, tüm bu problemi masadan kaldırıyor. MCP artık stateless.
Tam olarak ne değişti
Protokol seviyesindeki oturum gitti. Artık initialize/initialized el sıkışması yok,
Streamable HTTP transport'unda Mcp-Session-Id başlığı yok. Her istek artık kendi başına
duruyor ve ihtiyacı olanı _meta içinde taşıyor - protokol versiyonunu, istemcinin
yeteneklerini, istemcinin kim olduğunu:
POST /mcp HTTP/1.1
Mcp-Method: tools/call
Mcp-Name: scan_repository
Content-Type: application/json
{ "method": "tools/call",
"params": { "name": "scan_repository", "arguments": { "repo": "acme/api" } },
"_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28" } }
İki yeni başlık, Mcp-Method ve Mcp-Name, artık Streamable HTTP POST'larında zorunlu. Bu
ayrıntı küçük görünüyor ama asıl mesele bu: bir gateway, rate limiter ya da router artık bir
istekle ne yapacağına iki başlığı okuyarak, JSON gövdesini hiç ayrıştırmadan karar verebilir.
Herhangi bir istek, düz bir round-robin balancer arkasında herhangi bir instance'a düşebilir.
Sticky-session problemi diye bir şey artık yok.
En sevdiğim kısım, gerçekten çağrılar arası kalıcı olması gereken state'e ne olduğu. Onu transport'un içine saklamak yerine, sunucu açık bir handle üretip döndürüyor ve model bir sonraki çağrıda bunu sıradan bir araç argümanı olarak geri veriyor. State, modelin görebildiği ve araçlar arasında taşıyabildiği görünür bir değere dönüşüyor; bağlantıya cıvatalanmış görünmez bir şey değil. Daha önce yazdıklarımdan birini okuduysan, örtük olanı açık yapmaya bir zaafım olduğunu bilirsin. İşte bu, tam da o, protokol seviyesinde.
Sunucu tarafından başlatılan istekler de Multi Round-Trip Requests adlı yeni bir örüntüyle aynı
muameleyi görüyor. Sunucunun istemciye geri çağrı yapmak için bir stream'i açık tutması (eski
sampling/createMessage, elicitation/create, roots/list) yerine, daha fazla girdiye
ihtiyaç duyan bir araç sadece resultType: "input_required" ve bir inputRequests alanı olan
bir sonuç döndürüyor. İstemci de orijinal isteği inputResponses ekleyerek yeniden deneyip
cevap veriyor. Artık baştan sona bir istek/yanıt protokolü; bu da hakkında akıl yürütmesi ve
sıradan HTTP altyapısının arkasına koyması çok daha kolay bir şey.
Ne bozuluyor
Bedava öğle yemeği değil bu ve spec bu konuda dürüst.
- Stream resumability gitti. Artık
Last-Event-IDile yeniden oynatma yok. Bir yanıt stream'i istek ortasında koparsa, o istek kaybolur ve istemci onu yeni bir istek ID'siyle baştan göndermek zorunda. Yeniden bağlanmayı transport değil, sen hallediyorsun. - Roots, Sampling ve Logging deprecated oldu. Önerilen geçişler mantıklı: Roots yerine dosya ve dizinleri araç parametresi olarak geç, Sampling yerine LLM sağlayıcını doğrudan çağır, Logging özelliği yerine stderr'e ya da OpenTelemetry'ye logla.
- Eski HTTP+SSE transport'u artık resmen Deprecated, ayrıca
ping,logging/setLevelve birkaç bildirim tamamen kaldırıldı. Canlı değişiklik bildirimleri tek bir opt-insubscriptions/listenstream'ine taşınıyor, log seviyesi ise istek başına_metaiçinde ayarlanıyor. - Yeni bir
server/discoverRPC'si, yetenek keşfi için el sıkışmanın yerini alıyor. Sunucular bunu implemente etmek zorunda; istemciler protokol versiyonu seçmek için önceden çağırabilir.
Yeniden sınıflandırılan özellikler için on iki aylık bir deprecation penceresi var, yani hiçbir şey bir gecede kaybolmuyor; ama yön net.
Bugün ne yapardım
Spec günlük. Onunla ilgili en önemli tek gerçek bu. SDK'lar, gateway'ler, proxy'ler ve arasındaki her parça tooling'in yetişmesi zaman ister ve bu kadar taze bir protokole bir production entegrasyonunu doğrultup tüm yolun onu temiz işlediğini varsaymam.
Ama tasarım doğru ve şimdiden hazırlanmaya başlardım. Yeni bir sunucu yazıyorsan, en baştan stateless kur: bir oturuma bağımlılık yok, çağrılar arası state açık handle'larda taşınıyor, MRTR örüntüsüyle değiştirebileceğin sunucu-başlatımlı geri çağrı yok. Bu şekilde yazılmış kod, sticky-routing cambazlığı olmadan düz HTTP altyapısına oturur ve yeni spec'e karşı savaşmak yerine ona doğru yaşlanır. Stateful sürüm, istek/yanıt bir ortamı sohbet tutuyormuş gibi gösterme çabasının bir geçici çözümüydü hep. Protokolün nihayet bir isteğin sadece bir istek olduğunu kabul ettiğini görmek güzel.