MCP sunucum bir noktada durup çalışmayı bırakırdı. Hata dökümü (crash dump) yok. Loglarda stack trace yok. İstemciler şikayet etmeden bağlanırdı, sonra birkaç saat sonra her şey sessizliğe gömülürdü. İstekler kaybolur ve karşı uçtaki yapay zeka ajanı boşluktan başka bir şey almazdı.
Bu, Model Context Protocol (MCP) ekosisteminde sinir bozucu derecede yaygın bir hikayedir. Protokol, yapay zeka ajanlarının harici araçları nasıl keşfedeceğini ve çağıracağını tanımlar, ancak spesifikasyon hataları kendinizin yöneteceğini varsayar. Çoğu eğitim ve başlangıç uygulaması bu kısmı atlar. Onlar "mutlu yol"a (happy path) odaklanırlar: bir fonksiyonu işaretleyin, sunucu üzerinden dışa açın ve temiz bir sonuç döndürün. Harici API'nizde bir ağ dalgalanması olduğunda veya model bir parametre adını uydurduğunda (hallucinate) ve geçersiz girdi gönderdiğinde neler olacağını nadiren gösterirler. Sonuç; sağlıklı görünen ancak aslında saatlerdir ölü olan kırılgan bir sunucudur.
Boş Yanıtlar Neden Çökmelerden Daha Kötüdür
Bir MCP araç işleyicisinde (tool handler) yakalanmamış bir istisna (exception) sızdığında, taşıma katmanı (transport layer) bunu genellikle yutar. Sunucu süreci canlı kalır, soket açık kalır ancak istemci boş bir yanıt alır. Bu, gürültülü bir çökmeden daha tehlikelidir çünkü izleme (monitoring) sisteminiz bunu fark etmeyebilir. Süreç hâlâ çalışıyordur. Port hâlâ dinlemededir. Yine de her araç çağrısı hiçbir şey döndürmez.
Yapay zeka modeli sessizliği bir başarısızlık olarak yorumlamaz. Sessizliği, veri üretmeyen başarılı bir çağrı olarak yorumlar. Bu boş yanıt, modeli doğaçlama yapmaya alıştırır. Boşluğu doldurmak için gerçek olmayan bilgiler uydurmaya (hallucinate) başlar veya aynı hatalı çağrıyı tekrar eden bir döngüye girer. Geçici bir ağ zaman aşımı veya geçersiz bir araç argümanı gibi küçük sorunların bu tür bir davranışa neden olmasına asla izin verilmemelidir.
Wrapper Deseni: Üç Savunma Hattı
Bunu, her araç işleyicisini ince bir hata kurtarma katmanıyla (error-recovery layer) sarmalayarak (wrapping) çözdüm. Wrapper, her olası hatayı tahmin etmeye çalışmaz. Onları kategorize eder ve buna göre yanıt verir.
ConnectionError ve TimeoutError
Bunlar, sunucunuz harici bir API ile konuşurken ağ dalgalandığında ortaya çıkar. İçgüdüsel çözüm, tüm MCP sunucu sürecini yeniden başlatmaktır. Bunu yapmayın. Yeniden başlatmak aktif istemci bağlantılarını düşürür, bellekteki tüm durumu temizler ve tam bir yeniden başlatmaya (re-initialization) zorlar. Bunun yerine, bağlantı hatasını yakalayın ve yalnızca aracınızın kullandığı taşıma katmanını veya HTTP istemcisini yeniden bağlayın. Sunucu sıcak kalır ve bir sonraki istek için hemen hazır olur.
ValueError
Bu, yapay zeka istemcisi hatalı argümanlar gönderdiğinde gördüğünüz şeydir. Belki model bir parametre uydurdu, bir tam sayı gereken yere bir dize (string) gönderdi veya gerekli bir alanı unuttu. Eğer bunun yakalanmadan yukarı sızmasına (bubble up) izin verirseniz, istemci ya bir çökme ya da boş bir yanıt alır. Bunu wrapper içinde yakalayın, ardından modele tam olarak neyin yanlış gittiğini söyleyen net ve spesifik bir mesaj oluşturun. Hangi parametrenin başarısız olduğunu ve neyin beklendiğini açıklayın. Çoğu modern yapay zeka modeli bu mesajı okuyacak ve bir sonraki adımda kendi hatasını düzeltecektir. Belirsiz bir hata, bir muhakeme döngüsünü boşa harcar. Kesin bir hata, sorunu anında çözer.
General Exceptions
Bir güvenlik ağı oluşturun. Eğer bir hata yukarıdaki kategorilerin dışındaysa, ayrıntıları kendiniz için günlüğe kaydedin (log) ve istemciye temiz, genel bir hata yanıtı döndürün. Bu, tuhaf bir uç durumun (edge case) herkes için oturumu sonlandırmasını engeller. Sunucu hayatta kalır, istemci bir şeyin başarısız olduğuna dair bir sinyal alır ve siz de daha sonra hata ayıklamak (debug) için günlüklerinizde yeterli bağlamı tutarsınız.
isError Bayrağı Tartışmaya Kapalıdır
İşte çözümünüzün işe yarayıp yaramayacağını asıl belirleyen detay: MCP yanıtları bir isError boolean alanı içerir. Eğer bir istisna oluşursa ve isError değerini true olarak ayarlamadan bir hata mesajı döndürürseniz, istemci bu hata metnini başarılı bir araç sonucu olarak kabul eder.
Harici API'nizin bir hız sınırına (rate limit) takıldığını hayal edin. İstisnayı yakalarsınız ve "API rate limit exceeded" dizesini döndürürsünüz ancak isError değerini false olarak bırakırsınız. İstemci, bu dizeyi sanki gerçek bir araç çıktısıymış gibi modelin bağlam penceresine (context window) iletir. Model daha sonra bu metni sanki bir veriymiş gibi analiz etmeye çalışır. Hatayı bir özette alıntılayabilir veya daha kötüsü, bu hata metni ile diğer gerçekler arasında uydurma ilişkiler kurabilir. Geçici bir altyapı aksaklığını bir yanlış bilgi kaynağına dönüştürmüş olursunuz.
Always set isError to true when you are returning an error payload. This gives the client a clear signal that the tool call failed, which lets the model decide whether to retry, ask for clarification, or try a different tool entirely.
Know What to Catch and What to Kill
Do not wrap your entire server in a blind try-catch that swallows everything. Some errors mean the server should stop immediately. If a required environment variable is missing on startup, or your configuration file is corrupt, no amount of request-level catching will help. Create a specific exception class for fatal errors like these and let them crash the process.
The rule is simple. If the error is temporary or isolated to a single request, catch it and recover. If the error means every subsequent request is guaranteed to fail, let the server die loudly. A fast failure on startup is infinitely better than a server that limps along for days in a broken state.
Add Observability Before You Need It
Once you have the wrapper in place, pair it with structured logging. Log every tool call and its outcome in JSON format. Include the tool name, the raw arguments, the latency, and whether it succeeded, failed, or retried.
This discipline pays off quickly. When you notice a spike in errors, you can filter by tool and spot patterns in minutes. Maybe a specific external API starts throwing timeouts at the same time every day, pointing to a scheduled maintenance window you did not know about. Maybe one tool receives consistently malformed arguments, revealing a prompt engineering flaw upstream. Plain text logs buried in stack traces make this detective work painful. Structured JSON makes it trivial.
The Production Result
I have run this wrapper pattern on two production MCP servers for the past three weeks. In that window, I have seen zero silent failures. Before adding the wrapper, I averaged roughly one unexplained failure every day. The pattern is not complex, but its impact is outsized because it separates survivable noise from real problems.
Silent failures cost more than crashes. A crash triggers your alerting system. Silence just erodes trust. One day your AI agent returns useful tool data, and the next day it starts making things up because the server stopped answering hours ago. The wrapper pattern closes that gap. It keeps your server running through minor turbulence, gives the model enough context to fix its own mistakes, and ensures that when something truly fatal goes wrong, you hear about it immediately.
If you are building MCP tools today, start with the wrapper and the isError flag. Everything else is just cleanup.
