← Blog
AI & DEVOPS·1 AĞUSTOS 2026AUG 1, 2026·12 DK OKUMA12 MIN READ

NestJS ile production'da stateless MCP server: auth, session'lar ve state nereye gittiRunning a stateless MCP server in production with NestJS: auth, sessions, and where the state went

28 Temmuz 2026'da MCP, session'ları ve initialize handshake'ini kaldırdı. Bu yazı, revizyonun deployment katmanında neyi değiştirdiğini, neyi sessizce size geri devrettiğini ve düz bir round-robin load balancer arkasında NestJS ile ne ölçtüğümüzü anlatıyor.

On July 28, 2026, MCP dropped sessions and the initialize handshake. This post covers what the revision changes at the deployment layer, what it quietly hands back to you, and what we measured behind a plain round-robin load balancer with NestJS.

Naylalabs · MühendislikEngineering
Snapshot: Bu yazı MCP'nin 2026-07-28 spesifikasyonu esas alınarak yazıldı; son doğrulama: 1 Ağustos 2026. MCP hâlâ hızlı değişiyor; o tarihten sonra değişen her şeyi yazının sonundaki changelog takip ediyor. Kod: agent-native-ops-demo. Snapshot: Written against the MCP 2026-07-28 specification; last verified August 1, 2026. MCP still moves fast; the changelog at the bottom tracks anything that changed since. Code: agent-native-ops-demo.
Agent-Native Operations · 1/5. Beş bölümlük serinin 1. yazısı — 1. Stateless MCP server · 2. Tool contract'ları · 3. Agent kimliği · 4. OpenTelemetry GenAI · 5. Auto-remediation guardrail'leri Agent-Native Operations · 1/5. Part 1 of a five-part series — 1. Stateless MCP server · 2. Tool contracts · 3. Agent identity · 4. OpenTelemetry GenAI · 5. Auto-remediation guardrails

28 Temmuz 2026'da Model Context Protocol, operasyonel olarak en pahalı iki fikrini çöpe attı: protokol seviyesindeki session ve initialize handshake'i. O tarihten önce bir remote MCP server işlettiyseniz, altyapınızın bir kısmı — sticky-session konfigürasyonu, paylaşımlı session store'u, trafiği yönlendirebilmek için JSON-RPC gövdesini açıp okumak zorunda kalan gateway — artık legacy.

On July 28, 2026, the Model Context Protocol dropped its two most operationally expensive ideas: the protocol-level session and the initialize handshake. If you ran a remote MCP server before that date, some of your infrastructure — the sticky-session config, the shared session store, the gateway that had to inspect JSON-RPC bodies to route traffic — is now legacy.

Bu, breaking change'in makbul türü. Ama göründüğü kadar bedava da değil. Session store'unuzun tuttuğu state yok olmadı; yer değiştirdi ve artık sizin uygulamanızın problemi. MCP'ye yabancıysanız önce MCP nedir yazısına bakın; burada protokolü değil, onu production'da işletmeyi konuşacağız.

This is the good kind of breaking change. It is also less free than it looks. The state your session store used to hold did not disappear; it moved, and it is now your application's problem. If MCP is new to you, start with what MCP is; here we talk not about the protocol but about operating it in production.

012026-07-28 revizyonu gerçekte neyi değiştirdiWhat the 2026-07-28 revision actually changed

Kısa versiyon, resmi changelog'dan:

The short version, from the official changelog:

istek yaşam döngüsü — öncesi / sonrasırequest lifecycle — before / after
2025-11-25·SESSION'LISESSION-BOUND
Client
initialize
Replica AMcp-Session-Id: abc
session yok ✕no session ✕
Replica B
sticky session / paylaşımlı store şart sticky sessions / shared store required
2026-07-28·STATELESS
Client
istek + _metarequest + _meta
LBround-robin
herhangi biriany replica
A
B
C
affinity yok · initialize yok · her istek kendine yeter no affinity · no initialize · every request self-contained
Şekil 1. Protokol session'ı kalkınca istekler herhangi bir replica'ya düşebilir; sticky session ve paylaşımlı session store gereksinim listesinden çıkar. Figure 1. With the protocol session gone, requests can land on any replica; sticky sessions and the shared session store drop off the requirements list.

Migration planlamadan önce bir nüans: hedeflediğiniz spec revizyonunun yayın durumunu kontrol edin. MCP blog'u 2026-07-28 için release-candidate yazısını da final-spec yazısını da birkaç gün arayla yayınladı ve SDK desteği önce beta olarak çıktı. Sunucunuzun server/discover içinde beyan ettiği revizyon string'ini sabitleyin ve bunu dokümantasyonunuzda açıkça söyleyin.

One nuance before you plan a migration: verify the release status of the spec revision you are targeting. The MCP blog published both a release-candidate post and a final-spec post for 2026-07-28 within days of each other, and SDK support shipped as betas first. Pin the exact revision string your server claims in server/discover, and say it out loud in your docs.

02"Stateless", "state yok" demek değil — "state artık senin" demek"Stateless" does not mean "no state" — it means the state is now yours

MCP session'ı örtük bir sözleşmeydi: sunucu, bu client'a bu bağlantıda ne söylediğini hatırlayabilirdi. O sözleşmenin bedeli sticky session'lar, session-affinity bug'ları ve her istekte danışılan paylaşımlı bir store'du.

An MCP session used to be an implicit contract: the server could remember what it told this client on this connection. That contract paid for sticky sessions, session-affinity bugs, and a shared store consulted on every request.

Yerine gelen şey bilinçli olarak sıkıcı: bir tool'un sürekliliğe ihtiyacı varsa — pagination cursor'ı, bir sepet, uzun süren bir iş — sunucu bir handle (opak bir string) üretip döner. Client bir sonraki çağrıda bu handle'ı sıradan bir tool argümanı olarak geri gönderir. State hâlâ var; handle'larınızı sırtlayan storage'da yaşıyor ve protokol bunu artık sizden saklamıyor.

The replacement is deliberately boring: if a tool needs continuity — a pagination cursor, a shopping cart, a long-running job — the server mints a handle (an opaque string) and returns it. The client passes it back as a normal tool argument on the next call. State still exists; it lives in whatever storage backs your handles, and the protocol no longer hides that from you.

Öncesi (protokol session'ları)Before (protocol sessions)Sonrası (server-minted handle'lar)After (server-minted handles)
Storage maliyetiStorage costSession store'a her istekte gidilirSession store consulted on every requestSadece state isteyen tool'lardaOnly on tools that need state
YönlendirmeRoutingAffinity şart ya da global latency bedelli paylaşımlı storeAffinity required, or shared store with global latency costHer replica her isteği karşılayabilirAny replica can serve any request
ÖmürLifetimeBağlantı semantiği, bulanıkConnection semantics, fuzzySizin TTL politikanız, explicitYour TTL policy, explicit
GörünürlükVisibilityTool şemasında görünmezInvisible in the tool schemaŞemada görünür — sözleşme, state'ini gösterirVisible in the schema — the contract shows its state

Son satır göründüğünden önemli: handle'lar artık tool şemalarınızda duruyor, yani state sadece bir ops meselesi değil, bir tasarım meselesi. Bir handle'ın ne vaat ettiği, ne kadar yaşadığı ve bir agent onu replay ettiğinde ne olacağı, serinin tool contract tasarımı yazısının konusu. Burada sadece nereye koyduğumuzu söyleyelim: sert TTL'li, {tenant}:{handle} ile anahtarlanmış bir Redis hash'i — çünkü pod restart'ından sağ çıkan handle, state'i process dışına taşımanın zaten bütün amacı.

That last row matters more than it looks: handles now live in your tool schemas, which makes state a design concern, not just an ops concern. What a handle promises, how long it lives, and what happens when an agent replays it belongs to this series' tool-contract article. Here we will only say where we put them: a Redis hash with a hard TTL, keyed by {tenant}:{handle} — a handle that survives a pod restart is the whole point of moving state out of the process.

handle-store.service.ts
// "State nereye gitti" sorusunun operasyonel yarısı.// The operational half of the "where did the state go" question.
// Handle'lar client için opak; yine de versiyonlayın (h1_ prefix'i).// Handles are opaque to the client; version them anyway (the h1_ prefix).
@Injectable()
export class HandleStore {
  constructor(@Inject(REDIS) private readonly redis: Redis) {}

  async mint(tenantId: string, payload: object, ttlSeconds: number): Promise<string> {
    const handle = `h1_${randomUUID()}`;
    await this.redis.set(`${tenantId}:${handle}`, JSON.stringify(payload), 'EX', ttlSeconds);
    return handle;
  }

  async resolve<T>(tenantId: string, handle: string): Promise<T | null> {
    const raw = await this.redis.get(`${tenantId}:${handle}`);
    return raw ? (JSON.parse(raw) as T) : null;
  }
}

Tenant prefix'ine dikkat. Session olmayınca, bir handle'ı çağırana bağlayan tek şey sizin authorization katmanınız — tenant'lar arası sızan bir handle, ucuna agent bağlanmış bir IDOR açığıdır. Bir handle'ın hangi agent kimliğine bağlı olduğu ve kotaların ona nasıl iliştiği ise serinin kimlik yazısının konusu.

Mind the tenant prefix. Without sessions, the only thing binding a handle to a caller is your authorization layer — a handle leaked across tenants is an IDOR bug with an agent attached. Which agent identity a handle is bound to, and how quotas attach to it, is this series' identity article.

03Nihayet sahip olabileceğiniz deployment topolojisiThe deployment topology you can finally have

Temmuz öncesinde, yatay ölçeklenen bir MCP server şunlardan birine mecburdu: load balancer'da sticky session, hot path üzerinde paylaşımlı session store, ya da metoda göre yönlendirme yapabilmek için JSON-RPC gövdesini açan bir gateway. Üçü de gereksinim listesinden düştü. 2026-07-28 topolojisi, on yıllık REST servislerinden zaten bildiğiniz topoloji:

Pre-July, a horizontally scaled MCP server needed one of: sticky sessions at the load balancer, a shared session store on the hot path, or a gateway doing deep packet inspection to route by method. All three dropped off the requirements list. The 2026-07-28 topology is the one you already know from a decade of REST services:

hedef topoloji — k8starget topology — k8s
Client
POST /mcp
Ingressroute: Mcp-Methodroutes on Mcp-Method
round-robin
pod A
pod B
pod C
pod'lar
pods
handle
Redissadece stateful tool'larstateful tools only
token exchange
Upstream APIbkz. bölüm 04see section 04
sticky yok · gövde parse eden gateway yok no sticky · no body-parsing gateway
Şekil 2. Ingress Mcp-Method header'ına bakarak yönlendirir; Redis hot path'in dışında, yalnızca handle'lı tool'lar dokunur. Figure 2. The ingress routes on the Mcp-Method header; Redis sits off the hot path, touched only by handle-backed tools.

Topolojiyi bu kadar temizleyen iki spec detayı var. Birincisi, routing artık header üstünden: her Streamable HTTP POST Mcp-Method (tool çağrılarında ek olarak Mcp-Name) taşıyor. Ingress'iniz tools/list'i cache'li katmana, tools/call'u worker katmanına, gövdeye hiç dokunmadan gönderebilir:

Two spec details make the topology this clean. First, routing happens on headers now: every Streamable HTTP POST carries Mcp-Method (plus Mcp-Name for tool calls). Your ingress can send tools/list to a cached tier and tools/call to the worker tier without touching the body:

ingress-route.yaml
# list trafiği cache'lenebilir katmana# route list traffic to a cacheable layer
- match:
    headers:
      Mcp-Method: { exact: "tools/list" }
  route: { destination: mcp-list-tier }
- route: { destination: mcp-worker-tier }

İkincisi, liveness yine düz HTTP: protokol seviyesindeki ping kaldırıldı. Probe'lar için sıradan bir /healthz kullanın ve MCP endpoint'ini health-check yolunun dışında tutun; NestJS'te bu, @nestjs/terminus'un her zamanki işi.

Second, liveness is plain HTTP again: the protocol-level ping is removed. Use an ordinary /healthz for probes and keep the MCP endpoint out of the health-check path; in NestJS that is @nestjs/terminus doing what it always did.

Sunucunun kendisindeki en büyük sadeleşme, silebildikleriniz: initialize state'ini takip eden connection-lifecycle servisi ve Mcp-Session-Id okuyan her guard. Uzun ömürlü tek route kalıyor: eski GET endpoint'inin yerini alan tek POST stream'i subscriptions/listen. Ona cömert idle timeout verin; proxy'niz zorluk çıkarıyorsa küçük, ayrı bir tier'a yönlendirin.

The biggest simplification in the server itself is what you get to delete: the connection-lifecycle service tracking initialize state and every guard reading Mcp-Session-Id. One long-lived route remains: subscriptions/listen, the single POST stream that replaced the old GET endpoint. Give it generous idle timeouts, and route it to a small dedicated tier if your proxy makes that awkward.

NestJS'te bunu sıfırdan yazmak zorunda da değilsiniz: MCP-Nest, 2026-07-28 desteğini dual-era modda getiriyor — aynı /mcp endpoint'i, aynı @Tool metodlarından, hem eski protokolü hem stateless revizyonu aynı anda cevaplıyor (protocol: 'dual'). Bu, aşağıdaki migration checklist'inin "pencerede iki davranışı birden çalıştırın" maddesinin hazır implementasyonu; demo repo da bu kurulumu kullanıyor.

You do not have to write this from scratch in NestJS either: MCP-Nest ships 2026-07-28 support in dual-era mode — the same /mcp endpoint answers both the old protocol and the stateless revision from the same @Tool methods (protocol: 'dual'). It is a ready-made implementation of the migration checklist's "run both behaviors during the window" item; the demo repo uses this setup.

04Production'da auth: two-hop problemi bir yere gitmediAuth in production: the two-hop problem did not go away

Statelessness transport'u sadeleştirdi. Asıl on-call çağrısı üreten kısmı, yani authorization'ı hiç sadeleştirmedi. Production'daki bir MCP server iki OAuth ilişkisinin ortasında oturur ve bu ikisi tek ilişkiye indirgenmeyi reddeder:

Statelessness simplified transport. It did nothing to simplify the part that actually generates on-call pages: authorization. A production MCP server sits in the middle of two OAuth relationships, and they refuse to collapse into one:

two-hop auth akışıtwo-hop auth flow
HOP 1 — agent → sizin sunucunuzHOP 1 — agent → your server
1AgentAuth Serverauthorize — client, cevaptaki iss'i doğrular (RFC 9207)authorize — the client validates iss on the response (RFC 9207)
2Auth ServerAgentaccess token
3AgentMCPtools/call + Bearer — self-contained istektools/call + Bearer — self-contained request
4MCPtoken doğrula — JWKS cache'li; amortize edecek session yokvalidate token — JWKS cached; no session to amortize over
HOP 2 — sizin sunucunuz → upstreamHOP 2 — your server → upstream
5MCPAuth ServerRFC 8693 token exchange (upstream destekliyorsa)RFC 8693 token exchange (where the upstream supports it)
6Auth ServerMCPupstream-audience tokenupstream-audience token
7MCPUpstreamtakaslı token ile çağrı → sonuç → resultType: completecall with the exchanged token → result → resultType: complete
Şekil 3. Hop 1 sıkı ayarlarla çözülmüş bir problem; hop 2 upstream başına ayrı bir pazarlık. Ekiplerin yandığı yer, spec'in hop 2'yi çözdüğünü varsaymak — çözmedi, çözdüğünü de iddia etmiyor. Figure 3. Hop 1 is a solved problem with strict settings; hop 2 is a per-upstream negotiation. Teams get burned assuming the spec solved hop 2 — it did not, and does not claim to.

2026-07-28 sıkılaştırması hop 1'de geçerli: credential'lar onları üreten issuer'a bağlı — bir authorization server'dan alınmış credential başka birine karşı kullanılamaz, issuer değişirse yeniden kayıt zorunlu. Entegratörleri ısıracak deprecation ise şu: Dynamic Client Registration kapıya kadar geldi; yerini, client kimliğinin kendi kontrol ettiği bir URL olduğu Client ID Metadata Documents alıyor. Onboarding dokümanlarınız agent geliştiricilerine "DCR yapın" diyorsa, o yolu şimdiden taşımaya başlayın — pencere on iki ay.

The 2026-07-28 hardening applies on hop 1: credentials are bound to the issuer that minted them — a credential from one authorization server must never be replayed against another, and an issuer change forces re-registration. The deprecation that will bite integrators: Dynamic Client Registration is on its way out, replaced by Client ID Metadata Documents, where the client's identity is a URL it controls. If your onboarding docs tell agent developers to "do DCR", start migrating that path now — the window is twelve months.

05Retry'lar artık sizin probleminiz — ve doğru adres orasıRetries are now your problem — and that is the correct place for them

On-call odasının duvarına poster yapılacak değişiklik bu. 2025-11-25'te kopan bir SSE stream'i Last-Event-ID ile devam ettirilebilirdi. 2026-07-28'de resumability kaldırıldı: kopan response stream'i uçuştaki isteği kaybeder ve client bunu yeni bir request ID ile yeni bir istek olarak tekrar göndermek zorundadır.

This is the change we would put on a poster in the on-call room. Under 2025-11-25, a dropped SSE stream could be resumed with Last-Event-ID. Under 2026-07-28, resumability is removed: a broken response stream loses the in-flight request, and the client MUST re-issue it as a new request with a new request ID.

Bunu bir operatör olarak okuyun: bir tools/call sırasındaki her ağ hıçkırığı artık ikinci bir çalıştırma denemesi üretiyor — ilkine protokol seviyesinde hiçbir bağı olmadan. Rolling deploy sırasında bu bir edge case değil; ana senaryo. Üç sonuç:

Read that as an operator: every network blip during a tools/call now produces a second execution attempt with no protocol-level linkage to the first. During a rolling deploy that is not an edge case — it is the main case. Three consequences:

Bu bir tahmin değil; ölçtük. Aşağıda 07'de tablonun tamamı var: sekiz crash'lik bir koşuda naif tool niyetlerin %1,37'sinde duplicate fatura üretti; aynı koşullarda idempotency key bunu %0,05'e indirdi.

This is not a prediction; we measured it. The full table is in section 07: across a run with eight crashes, the naive tool produced duplicate invoices on 1.37% of intents; under identical conditions an idempotency key cut that to 0.05%.

06Caching: spec nihayet CDN'in size yardım etmesine izin veriyorCaching: the spec finally lets a CDN help you

List cevapları artık bağlantıya göre değişmediği için, ilk kez dürüst birer HTTP kaynağı. CacheableResult bunu explicit yapıyor: her list cevabı ttlMs ve public/private değerli bir cacheScope taşıyor. Dikkat: varsayılan, muhafazakâr olan — ttlMs: 0, cacheScope: private. Yani caching'e bilinçli opt-in gerekiyor; demo repo'da bunu cacheHints ile yapıyoruz.

Because list responses no longer vary per connection, they are honest HTTP resources for the first time. CacheableResult makes it explicit: every list response carries ttlMs and a cacheScope of public/private. Note the default is the conservative one — ttlMs: 0, cacheScope: private — so caching is a deliberate opt-in; the demo repo does it via cacheHints.

Paylaşımlı cache'in origin trafiğine etkisini demo repo'nun 4. parçasıyla ölçeceğiz; sonuç geldiğinde bu yazının changelog'u söyleyecek.

We will measure the shared cache's effect on origin traffic with part 4 of the demo repo; the changelog of this post will say when the numbers land.

07Ne ölçtükWhat we measured

Tüm sayılar demo repo'dan. Ortam konusunda dürüst olalım: lokal bir kind cluster'ı (tek node, Docker Desktop, Apple Silicon), pod başına 1 CPU limiti, k6 aynı makinede. Bu bir datacenter benchmark'ı değil — mekanizma kanıtı. Senaryolar ve analiz script'i repo'da; kendi ortamınızda tekrarlayabilirsiniz.

All numbers come from the demo repo. Let's be honest about the environment: a local kind cluster (single node, Docker Desktop, Apple Silicon), a 1-CPU limit per pod, k6 on the same machine. This is not a datacenter benchmark — it is mechanism evidence. The scenarios and the analysis script are in the repo; reproduce them on your own setup.

Sabit yük, 1'e karşı 3 replica (echo_upper, round-robin):

Steady load, 1 vs 3 replicas (echo_upper, round-robin):

YükLoad1 replica · p95 / p993 replica · p95 / p99
100 rps · 60 sn6.7 ms / 21.1 ms7.4 ms / 30.8 ms
600 rps · 45 sn14.2 ms / 51.6 ms16.2 ms / 52.4 ms

Sizi şaşırtacak dürüst bulgu: bu iş yükünde yatay ölçeklemenin latency getirisi yok — ucuz bir tool'da tek Node process'i 600 rps'i tek başına karşılıyor. "Affinity yok" kazancını latency tablosunda değil, bir alttaki tabloda arayın: crash'ler sırasında servis ayakta kalıyor.

The honest finding that may surprise you: horizontal scaling buys no latency here — on a cheap tool, a single Node process handles 600 rps on its own. Look for the "no affinity" dividend not in the latency table but in the one below: the service stays up through crashes.

Pod-kill duplicate deneyi — 30 rps fatura niyeti, 90 sn, commit-sonrası 500 ms cevap penceresi; koşu başına 8 kez rastgele pod'lar crash ettirildi (POST /chaos/crash). Client, spec'in zorunlu kıldığı gibi kopan isteği yeni request-id ile tekrar gönderiyor:

Pod-kill duplicate experiment — 30 rps of invoice intents for 90 s, a 500 ms post-commit response window; 8 random pod crashes per run (POST /chaos/crash). The client re-issues broken requests with a new request ID, exactly as the spec mandates:

KoşuRunNiyetIntentsRetryDuplicate faturaDuplicate invoicesOranRate
create_invoice_naive2 70013237%1.37
create_invoice + idempotency_key2 1081 1211%0.05

Üç okuma: (1) Sekiz crash'e rağmen kaybolan niyet naifte 0/2700, key'lide 1/2108 — üç replica servis sürekliliğini taşıdı; ölçeklemenin gerçek getirisi bu. (2) Naif tool'da her 73 niyetten biri çift fatura kesti — %1,37 "nadir" gibi okunuyorsa, gününüzdeki tool çağrısı sayısıyla çarpın. (3) Key'li koşudaki tek duplicate, dürüstlüğün en öğretici satırı: crash, iş commit'i ile idempotency kaydının arasına denk geldi. Read-then-write idempotency atomik değildir; gerçek exactly-once istiyorsanız iş commit'i ile idem kaydını aynı transaction'a koyun — bu da serinin tool contract yazısının konusu.

Three readings: (1) Despite eight crashes, lost intents were 0/2700 naive and 1/2108 keyed — three replicas carried service continuity; that is the real scaling dividend. (2) The naive tool double-billed one in every 73 intents — if 1.37% reads as "rare", multiply it by your daily tool-call volume. (3) The single duplicate in the keyed run is honesty's most instructive row: the crash landed between the business commit and the idempotency record. Read-then-write idempotency is not atomic; if you want true exactly-once, put the commit and the idem record in one transaction — which is this series' tool-contract article.

08Migration checklist'iMigration checklist

09Sık sorulanlarFAQ

2026-07-28 mevcut client'ları kırar mı?
initialize bekleyen eski client'lar, katı bir 2026-07-28 sunucusuna karşı başarısız olur. server/discover uyumluluk sondası olarak da çalışır; dış tüketiciniz varsa deprecation penceresinde iki davranışı birden çalıştırın (MCP-Nest'in dual-era modu tam olarak bu) ve hangi revizyonları kabul ettiğinizi yazın.

Does 2026-07-28 break existing clients?
Older clients expecting initialize fail against a strict 2026-07-28 server. server/discover doubles as a compatibility probe; if you have external consumers, run both behaviors during the deprecation window (MCP-Nest's dual-era mode is exactly this) and document which revisions you accept.

Protokol stateless ise Redis'e hâlâ ihtiyacım var mı?
Sadece gerçekten çağrılar arası state isteyen tool'lar için. Fark şu: maliyet artık her isteği session store'dan geçiren bir vergi değil, tool başına görünür bir kalem.

Do I still need Redis if the protocol is stateless?
Only for tools that genuinely need cross-call state. The difference: the cost is now a visible per-tool line item instead of a tax on every request through a session store.

MCP server'ımı sıradan bir CDN arkasına koyabilir miyim artık?
public list endpoint'leri için evet — ttlMs/cacheScope bunun için var. Tool çağrıları cache'lenemez POST'lar olarak kalıyor.

Can I put my MCP server behind a regular CDN now?
For public list endpoints, yes — that is what ttlMs/cacheScope are for. Tool calls remain uncacheable POSTs.

SSE resumability'nin kaldırılması bir gerileme değil mi?
Kayıplı ağlar için tartışılır. Ama nadiren doğru implemente edilen bir sunucu özelliğini, explicit bir client retry sözleşmesiyle takas ediyor — ve idempotency'yi tool tasarımına, yani ait olduğu yere itiyor.

Isn't removing SSE resumability a regression?
For lossy networks, arguably. But it trades a rarely-implemented-correctly server feature for an explicit client retry contract — and pushes idempotency into tool design, where it belongs.

10Changelog

MCPNestJSDevOpsProductionProduction2026-07-28