माझा MCP सर्व्हर अचानक काम करणे थांबवायचा. कोणताही क्रॅश डम्प (crash dump) नसायचा. लॉग्समध्ये कोणताही स्टॅक ट्रेस (stack trace) नसायचा. क्लायंट्स कोणतीही तक्रार न करता कनेक्टेड असायचे, पण काही तासांनंतर सर्व काही शांत व्हायचे. विनंत्या (requests) गायब व्हायच्या आणि पलीकडचा AI एजंटला फक्त रिकामी हवा (blank air) मिळायची.
Model Context Protocol (MCP) इकोसिस्टममध्ये ही एक अत्यंत त्रासदायक आणि सामान्य गोष्ट आहे. हा प्रोटोकॉल AI एजंट्स बाह्य टूल्स (external tools) कसे शोधतात आणि त्यांना कसे कॉल करतात हे परिभाषित करतो, परंतु स्पेसिफिकेशन असे गृहीत धरते की तुम्ही त्रुटी (errors) स्वतः हाताळाल. बहुतेक ट्युटोरियल्स आणि सुरुवातीची अंमलबजावणी (starter implementations) या भागाकडे दुर्लक्ष करतात. ते फक्त 'हॅपी पाथ'वर (happy path) लक्ष केंद्रित करतात: फंक्शनला अॅनोटेट करा, सर्व्हरद्वारे ते उपलब्ध करून द्या आणि एक स्वच्छ निकाल (clean result) परत करा. जेव्हा तुमच्या एक्सटर्नल API मध्ये नेटवर्कची समस्या येते किंवा जेव्हा मॉडेल पॅरामीटरचे नाव चुकीचे सांगते (hallucinates) आणि कचरा इनपुट (garbage input) पाठवते, तेव्हा काय होते हे ते क्वचितच दाखवतात. याचा परिणाम असा होतो की सर्व्हर वरवर निरोगी दिसतो, पण प्रत्यक्षात तो तासाभरापासून बंद झालेला असतो.
रिकाम्या प्रतिसादांमुळे (Blank Responses) क्रॅशपेक्षा जास्त धोका का असतो?
जेव्हा MCP टूल हँडलरमध्ये एखादी अनहँडल्ड एक्सेप्शन (unhandled exception) येते, तेव्हा ट्रान्सपोर्ट लेयर (transport layer) अनेकदा ती गिळून टाकते. सर्व्हर प्रोसेस जिवंत राहते, सॉकेट (socket) उघडे राहते, परंतु क्लायंटला रिकामी प्रतिक्रिया मिळते. हे एखाद्या मोठ्या क्रॅशपेक्षा जास्त धोकादायक आहे कारण तुमचे मॉनिटरिंग कदाचित ते लक्षात घेणार नाही. प्रोसेस अजूनही चालू असते. पोर्ट अजूनही ऐकत (listening) असते. तरीही प्रत्येक टूल कॉल काहीही परत करत नाही.
AI मॉडेल शांततेचा अर्थ अपयश असा घेत नाही. ते शांततेचा अर्थ असा घेते की कॉल यशस्वी झाला पण कोणताही डेटा तयार झाला नाही. तो रिकामी प्रतिसाद मॉडेलला स्वतःच्या मनाने काहीतरी तयार करण्यास (improvise) प्रवृत्त करतो. ती माहितीची पोकळी भरून काढण्यासाठी तथ्ये काल्पनिकपणे सांगू लागते (hallucinating facts), किंवा ती त्याच तुटलेल्या कॉलला वारंवार करण्याचा लूपमध्ये अडकते. तात्पुरता नेटवर्क टाइमआउट किंवा अवैध टूल आर्ग्युमेंट (invalid tool argument) यांसारख्या लहान समस्यांमुळे अशा प्रकारची वर्तणूक कधीही होऊ दिली जाऊ नये.
द Wrapper Pattern: संरक्षणाच्या तीन ओळी
मी प्रत्येक टूल हँडलरला एका पातळ एरर-रिकव्हरी लेयरमध्ये (error-recovery layer) गुंडाळून (wrap करून) ही समस्या सोडवली. हे रॅपर (wrapper) प्रत्येक संभाव्य अपयशाचा अंदाज घेण्याचा प्रयत्न करत नाही. ते त्यांना वर्गीकृत करते आणि त्यानुसार प्रतिसाद देते.
ConnectionError आणि TimeoutError
जेव्हा तुमचा सर्व्हर एखाद्या एक्सटर्नल API शी संवाद साधतो आणि नेटवर्कमध्ये अडथळा येतो, तेव्हा हे त्रुटी उद्भवतात. अशा वेळी संपूर्ण MCP सर्व्हर प्रोसेस रीस्टार्ट करणे हा नैसर्गिक उपाय वाटतो. पण तसे करू नका. रीबूट केल्यामुळे सक्रिय क्लायंट कनेक्शन्स तुटतात, इन-मेमरी स्टेट (in-memory state) साफ होते आणि पूर्ण री-इनिशियलायझेशन (re-initialization) करावे लागते. त्याऐवजी, कनेक्शन फेल्युअर पकडा (catch करा) आणि फक्त तुमच्या टूलने वापरलेला ट्रान्सपोर्ट लेयर किंवा HTTP क्लायंट पुन्हा कनेक्ट करा. यामुळे सर्व्हर लगेच पुढच्या विनंतीसाठी तयार राहतो.
ValueError
जेव्हा AI क्लायंट चुकीचे आर्ग्युमेंट्स (malformed arguments) पाठवतो, तेव्हा तुम्हाला हे दिसते. कदाचित मॉडेलने एखादा नवीन पॅरामीटर तयार केला असेल, जिथे पूर्णांक (integer) आवश्यक होता तिथे स्ट्रिंग (string) पाठवली असेल, किंवा एखादे आवश्यक फील्ड विसरले असेल. जर तुम्ही याला अनहँडल्ड राहू दिले, तर क्लायंटला एकतर क्रॅश मिळेल किंवा रिकामी प्रतिक्रिया मिळेल. रॅपरच्या आत ही त्रुटी पकडा, आणि त्यानंतर मॉडेलला नेमके काय चुकले हे सांगणारा एक स्पष्ट आणि विशिष्ट संदेश तयार करा. कोणता पॅरामीटर फेल झाला आणि काय अपेक्षित होते हे स्पष्ट करा. बहुतेक आधुनिक AI मॉडेल्स तो संदेश वाचतील आणि पुढच्याच वेळी स्वतःहून सुधारणा करतील. अस्पष्ट त्रुटीमुळे विचार प्रक्रियेचा (reasoning cycle) वेळ वाया जातो. अचूक त्रुटी समस्या लगेच सोडवते.
General Exceptions
एक सेफ्टी नेट (safety net) ठेवा. जर एखादी त्रुटी वरील श्रेणींच्या बाहेर असेल, तर स्वतःसाठी तपशील लॉग करा आणि क्लायंटला एक स्वच्छ, सामान्य फेल्युअर प्रतिसाद (generic failure response) परत करा. यामुळे एखादी विचित्र एज केस (edge case) सर्वांसाठी सेशन संपवू शकणार नाही. सर्व्हर टिकून राहतो, क्लायंटला काहीतरी चुकले आहे असा संकेत मिळतो आणि तुम्ही नंतर डीबग करण्यासाठी तुमच्या लॉग्समध्ये पुरेसा संदर्भ (context) ठेवू शकता.
isError फ्लॅग अनिवार्य आहे
तुमचा उपाय काम करेल की नाही हे ठरवणारा हा महत्त्वाचा तपशील आहे. MCP प्रतिसादांमध्ये isError नावाचे बुलियन (boolean) फील्ड असते. जर एखादी एक्सेप्शन आली आणि तुम्ही isError true सेट न करता एरर मेसेज परत केला, तर क्लायंट त्या एरर टेक्स्टला टूलचा यशस्वी निकाल मानतो.
कल्पना करा की तुमच्या एक्सटर्नल API ने रेट लिमिट (rate limit) गाठली आहे. तुम्ही एक्सेप्शन पकडता आणि "API rate limit exceeded" हा स्ट्रिंग परत करता, परंतु isError 'false' ठेवता. क्लायंट तो स्ट्रिंग मॉडेलच्या कॉन्टेक्स्ट विंडोमध्ये (context window) असा पाठवतो जणू काही तो टूलचा खरा आउटपुट आहे. त्यानंतर मॉडेल त्या टेक्स्टवर डेटा असल्याप्रमाणे विचार करण्याचा प्रयत्न करते. ते सारांशामध्ये त्या त्रुटीचा उल्लेख करू शकते किंवा त्याहून वाईट म्हणजे, ते त्या एरर टेक्स्ट आणि इतर तथ्ये यांच्यात काल्पनिक संबंध जोडू शकते. तुम्ही पायाभूत सुविधांमधील (infrastructure) तात्पुरत्या अडथळ्याचे रूपांतर चुकीच्या माहितीच्या स्त्रोतात केले आहे.
जेव्हा तुम्ही error payload परत करत असाल, तेव्हा नेहमी isError ला true सेट करा. यामुळे क्लायंटला tool call अयशस्वी झाल्याचा स्पष्ट संकेत मिळतो, ज्यामुळे मॉडेलला पुन्हा प्रयत्न करायचा (retry), स्पष्टीकरण मागायचे की पूर्णपणे वेगळे tool वापरून पाहायचे, याचा निर्णय घेता येतो.
काय पकडायचे (Catch) आणि काय थांबवायचे (Kill) हे जाणून घ्या
तुमच्या संपूर्ण सर्व्हरला अशा कोणत्याही 'blind try-catch' मध्ये गुंडाळू नका जी सर्व काही गिळून टाकते. काही त्रुटींचा (errors) अर्थ असा असतो की सर्व्हरने त्वरित थांबले पाहिजे. जर स्टार्टअपच्या वेळी एखादा आवश्यक environment variable उपलब्ध नसेल किंवा तुमची configuration file खराब झाली असेल, तर request-level वर त्रुटी पकडल्याने काहीही उपयोग होणार नाही. अशा घातक (fatal) त्रुटींसाठी एक विशिष्ट exception class तयार करा आणि त्यांना प्रोसेस क्रॅश करू द्या.
नियम साधा आहे. जर त्रुटी तात्पुरती असेल किंवा केवळ एकाच request पर्यंत मर्यादित असेल, तर ती पकडा (catch) आणि पूर्ववत करा (recover). जर त्रुटीचा अर्थ असा असेल की त्यानंतर येणारे प्रत्येक request अयशस्वी होणारच आहे, तर सर्व्हरला स्पष्टपणे बंद पडू द्या. स्टार्टअपवर लवकर त्रुटी येणे हे तापावर चालणाऱ्या किंवा खराब अवस्थेत अनेक दिवस चालू राहणाऱ्या सर्व्हरपेक्षा कितीतरी पटीने चांगले आहे.
गरज पडण्यापूर्वीच Observability जोडा
एकदा का तुमच्याकडे wrapper तयार झाले की, त्याला structured logging सोबत जोडा. प्रत्येक tool call आणि त्याचा निकाल JSON फॉरमॅटमध्ये लॉग करा. यामध्ये tool चे नाव, raw arguments, latency आणि ते यशस्वी झाले, अयशस्वी झाले की पुन्हा प्रयत्न (retry) केले, याचा समावेश करा.
या शिस्तीचा फायदा लवकरच मिळतो. जेव्हा तुम्हाला त्रुटींमध्ये अचानक वाढ (spike) दिसून येईल, तेव्हा तुम्ही tool नुसार फिल्टर करून काही मिनिटांत पॅटर्न शोधू शकता. कदाचित एखादे विशिष्ट external API दररोज एकाच वेळी timeout देऊ लागते, जे तुम्हाला माहित नसलेल्या scheduled maintenance window कडे निर्देश करते. कदाचित एखादे tool सातत्याने चुकीचे (malformed) arguments प्राप्त करत असेल, ज्यामुळे upstream मधील prompt engineering मधील त्रुटी समोर येते. stack traces मध्ये गाडलेले plain text logs हे शोधकाम (detective work) कठीण करतात, तर structured JSON मुळे ते अत्यंत सोपे होते.
प्रोडक्शनचा निकाल
मी गेल्या तीन आठवड्यांपासून दोन production MCP सर्व्हरवर हा wrapper pattern वापरला आहे. या काळात, मला एकही silent failure पाहायला मिळाले नाही. wrapper जोडण्यापूर्वी, दररोज सरासरी एक न समजणारी त्रुटी येत असे. हा पॅटर्न गुंतागुंतीचा नाही, पण त्याचा प्रभाव खूप मोठा आहे कारण तो साध्या गोंधळाला (noise) खऱ्या समस्यांपासून वेगळे करतो.
Silent failures हे क्रॅशपेक्षा जास्त महाग पडतात. क्रॅशमुळे तुमचे alerting system सक्रिय होते. पण शांतता (silence) फक्त विश्वास कमी करते. एक दिवस तुमचा AI agent उपयुक्त tool data देतो आणि दुसऱ्या दिवशी तो स्वतःच्या मनाने गोष्टी सांगू लागतो कारण सर्व्हर तासांपूर्वीच प्रतिसाद देणे थांबवतो. wrapper pattern ही दरी भरून काढते. ते तुमच्या सर्व्हरला किरकोळ अडथळ्यांमधून (minor turbulence) चालू ठेवते, मॉडेलला स्वतःच्या चुका सुधारण्यासाठी पुरेसा संदर्भ (context) देते आणि जेव्हा खरोखरच काही गंभीर (fatal) घडते, तेव्हा तुम्हाला त्याबद्दल त्वरित माहिती मिळेल याची खात्री देते.
जर तुम्ही आज MCP tools बनवत असाल, तर wrapper आणि isError flag ने सुरुवात करा. बाकी सर्व गोष्टी फक्त cleanup आहेत.
