My MCP server used to simply stop working. No crash dump. No stack trace in the logs. Clients connected without complaint, then after a few hours the whole thing went mute. Requests disappeared and the AI agent on the other end received nothing but blank air.
This is a frustratingly common story in the Model Context Protocol (MCP) ecosystem. The protocol defines how AI agents discover and call external tools, but the specification assumes you will handle errors yourself. Most tutorials and starter implementations skip past that part. They focus on the happy path: annotate a function, expose it through the server, and return a clean result. They rarely show you what happens when a network blip hits your external API, or when the model hallucinates a parameter name and sends garbage input. The result is a brittle server that looks healthy but has actually been dead for hours.
Why Blank Responses Are Worse Than Crashes
When an unhandled exception slips through in an MCP tool handler, the transport layer often swallows it. The server process stays alive, the socket remains open, but the client gets an empty response. This is more dangerous than a loud crash because your monitoring might not notice. The process is still running. The port is still listening. Yet every tool call returns nothing.
The AI model does not interpret silence as failure. It interprets silence as a successful call that produced no data. That blank response trains the model to improvise. It starts hallucinating facts to fill the gap, or it enters a loop of retrying the same broken call. Small issues like a transient network timeout or an invalid tool argument should never be allowed to cause this kind of behavior.
The Wrapper Pattern: Three Lines of Defense
I fixed this by wrapping every tool handler in a thin error-recovery layer. The wrapper does not try to predict every possible failure. It categorizes them and responds accordingly.
ConnectionError and TimeoutError
These arise when your server talks to an external API and the network wobbles. The instinctive fix is to restart the entire MCP server process. Do not do that. Rebooting drops active client connections, clears any in-memory state, and forces a full re-initialization. Instead, catch the connection failure and reconnect only the transport layer or HTTP client your tool uses. The server stays warm and ready for the next request immediately.
ValueError
This is what you see when the AI client sends malformed arguments. Maybe the model invented a parameter, passed a string where an integer was required, or forgot a required field. If you let this bubble up unhandled, the client gets either a crash or a blank reply. Catch it inside the wrapper, then construct a clear, specific message that tells the model exactly what went wrong. Explain which parameter failed and what was expected. Most modern AI models will read that message and self-correct on the very next turn. A vague error wastes a reasoning cycle. A precise error fixes the problem immediately.
General Exceptions
Keep a safety net. If an error falls outside the categories above, log the details for yourself and return a clean, generic failure response to the client. This prevents one weird edge case from killing the session for everyone. The server survives, the client gets a signal that something failed, and you keep enough context in your logs to debug later.
The isError Flag Is Non-Negotiable
Here is the detail that actually determines whether your fix works. MCP responses include an isError boolean field. If an exception occurs and you return an error message without setting isError to true, the client treats that error text as a successful tool result.
Imagine your external API hits a rate limit. You catch the exception and return the string "API rate limit exceeded" but leave isError as false. The client passes that string into the model's context window as if it were real tool output. The model then tries to reason over that text as if it were data. It might quote the error in a summary, or worse, it might hallucinate relationships between that error text and other facts. You have turned a temporary infrastructure hiccup into a source of misinformation.
جب بھی آپ error payload واپس کر رہے ہوں، ہمیشہ isError کو true پر سیٹ کریں۔ یہ کلائنٹ کو ایک واضح اشارہ دیتا ہے کہ ٹول کال (tool call) ناکام ہو گئی ہے، جس سے ماڈل کو یہ فیصلہ کرنے میں مدد ملتی ہے کہ آیا اسے دوبارہ کوشش کرنی چاہیے، وضاحت مانگنی چاہیے، یا مکمل طور پر کوئی دوسرا ٹول استعمال کرنا چاہیے۔
جانیں کہ کسے سنبھالنا ہے اور کسے ختم کرنا ہے
اپنے پورے سرور کو ایک ایسے اندھے try-catch میں نہ لپیٹیں جو ہر چیز کو نگل جائے۔ کچھ غلطیوں کا مطلب یہ ہوتا ہے کہ سرور کو فوری طور پر رک جانا چاہیے۔ اگر اسٹارٹ اپ کے وقت کوئی ضروری environment variable موجود نہ ہو، یا آپ کی configuration فائل خراب ہو، تو ریکویسٹ لیول (request-level) پر کی جانے والی کوئی بھی کوشش کام نہیں آئے گی۔ اس طرح کی مہلک (fatal) غلطیوں کے لیے ایک مخصوص exception class بنائیں اور انہیں پروسیس کو کریش (crash) کرنے دیں۔
اصول سادہ ہے۔ اگر غلطی عارضی ہے یا صرف ایک ریکویسٹ تک محدود ہے، تو اسے سنبھالیں (catch کریں) اور بحال ہو جائیں۔ اگر غلطی کا مطلب یہ ہے کہ ہر آنے والی ریکویسٹ کے ناکام ہونے کا امکان ہے، تو سرور کو کھل کر کریش ہونے دیں۔ اسٹارٹ اپ پر فوری ناکامی اس سرور سے کہیں بہتر ہے جو کئی دنوں تک خراب حالت میں لڑکھڑاتا رہے۔
ضرورت پڑنے سے پہلے observability شامل کریں
ایک بار جب آپ کا wrapper تیار ہو جائے، تو اسے structured logging کے ساتھ جوڑ دیں۔ ہر tool call اور اس کے نتیجے کو JSON فارمیٹ میں لاگ (log) کریں۔ اس میں ٹول کا نام، خام (raw) arguments، latency، اور یہ شامل کریں کہ آیا وہ کامیاب رہا، ناکام ہوا، یا دوبارہ کوشش کی گئی۔
یہ نظم و ضبط جلد ہی فائدہ مند ثابت ہوتا ہے۔ جب آپ غلطیوں میں اضافہ محسوس کریں، تو آپ ٹول کے لحاظ سے فلٹر کر کے منٹوں میں پیٹرن (patterns) پہچان سکتے ہیں۔ ہو سکتا ہے کہ کوئی مخصوص بیرونی API روزانہ ایک ہی وقت پر timeout دے رہی ہو، جو کسی ایسے شیڈول شدہ مینٹیننس ونڈو کی طرف اشارہ کرتی ہو جس کے بارے میں آپ نہیں جانتے تھے۔ ہو سکتا ہے کہ کوئی ایک ٹول مسلسل غلط (malformed) arguments وصول کر رہا ہو، جو کہ اوپر کے مرحلے (upstream) میں prompt engineering کی کسی خامی کو ظاہر کرے۔ stack traces میں دبے ہوئے سادہ ٹیکسٹ لاگز اس تفتیشی کام کو تکلیف دہ بنا دیتے ہیں، جبکہ structured JSON اسے انتہائی آسان بنا دیتا ہے۔
پروڈکشن کا نتیجہ
میں نے گزشتہ تین ہفتوں سے دو پروڈکشن MCP سرورز پر اس wrapper pattern کا استعمال کیا ہے۔ اس دوران، میں نے ایک بھی خاموش ناکامی (silent failure) نہیں دیکھی۔ Wrapper شامل کرنے سے پہلے، اوسطاً روزانہ ایک ایسی ناکامی ہوتی تھی جس کی وجہ معلوم نہیں ہوتی تھی۔ یہ پیٹرن پیچیدہ نہیں ہے، لیکن اس کا اثر بہت زیادہ ہے کیونکہ یہ معمولی شور (noise) کو حقیقی مسائل سے الگ کر دیتا ہے۔
خاموش ناکامیاں کریش سے زیادہ مہنگی پڑتی ہیں۔ کریش آپ کے الرٹنگ سسٹم (alerting system) کو متحرک کر دیتا ہے، جبکہ خاموشی صرف اعتماد کو ختم کرتی ہے۔ ایک دن آپ کا AI ایجنٹ مفید ٹول ڈیٹا فراہم کرتا ہے، اور اگلے ہی دن وہ اپنی من گھڑت باتیں بنانا شروع کر دیتا ہے کیونکہ سرور گھنٹوں پہلے جواب دینا بند کر چکا ہوتا ہے۔ Wrapper pattern اس فرق کو ختم کر دیتا ہے۔ یہ آپ کے سرور کو معمولی اتار چڑھاؤ کے دوران بھی چلتا رکھتا ہے، ماڈل کو اپنی غلطیاں درست کرنے کے لیے کافی سیاق و سباق (context) فراہم کرتا ہے، اور اس بات کو یقینی بناتا ہے کہ جب کوئی واقعی مہلک مسئلہ پیش آئے، تو آپ کو فوری طور پر اس کا علم ہو جائے۔
اگر آپ آج MCP ٹولز بنا رہے ہیں، تو wrapper اور isError فلیگ سے آغاز کریں۔ باقی سب کچھ محض صفائی ستھرائی ہے۔
