আমার MCP সার্ভারটি হঠাৎ করেই কাজ করা বন্ধ করে দিত। কোনো ক্র্যাশ ডাম্প ছিল না। লগগুলোতে কোনো স্ট্যাক ট্রেসও ছিল না। ক্লায়েন্টরা কোনো অভিযোগ ছাড়াই কানেক্ট হতো, কিন্তু কয়েক ঘণ্টা পর পুরো সিস্টেমটি নিশ্চুপ হয়ে যেত। রিকোয়েস্টগুলো অদৃশ্য হয়ে যেত এবং ওপাশ থেকে AI এজেন্ট কিছুই পেত না।

এটি Model Context Protocol (MCP) ইকোসিস্টেমে একটি হতাশাজনকভাবে সাধারণ ঘটনা। প্রোটোকলটি নির্ধারণ করে যে কীভাবে AI এজেন্টরা এক্সটার্নাল টুলগুলো খুঁজে পায় এবং কল করে, কিন্তু স্পেসিফিকেশনটি ধরে নেয় যে আপনি নিজেই এররগুলো হ্যান্ডেল করবেন। বেশিরভাগ টিউটোরিয়াল এবং স্টার্টার ইমপ্লিমেন্টেশন এই অংশটি এড়িয়ে যায়। তারা স্বাভাবিক কার্যপ্রবাহ বা 'happy path'-এর ওপর মনোযোগ দেয়: একটি ফাংশন অ্যানোটেট করা, সার্ভারের মাধ্যমে এটি প্রকাশ করা এবং একটি পরিষ্কার ফলাফল প্রদান করা। আপনার এক্সটার্নাল API-তে নেটওয়ার্কের সামান্য সমস্যা হলে বা মডেল কোনো প্যারামিটার নাম নিয়ে হ্যালুসিনেশন করে ভুল ইনপুট পাঠালে কী ঘটে, তা তারা খুব কমই দেখায়। এর ফলে একটি ভঙ্গুর সার্ভার তৈরি হয় যা দেখতে সুস্থ মনে হলেও আসলে ঘণ্টার পর ঘণ্টা অচল হয়ে পড়ে থাকে।

কেন ব্ল্যাঙ্ক রেসপন্স ক্র্যাশের চেয়েও খারাপ

যখন একটি MCP টুল হ্যান্ডলারে কোনো আনহ্যান্ডেলড এক্সেপশন ঘটে, তখন ট্রান্সপোর্ট লেয়ার প্রায়শই সেটি ঢেকে ফেলে। সার্ভার প্রসেসটি সচল থাকে, সকেট খোলা থাকে, কিন্তু ক্লায়েন্ট একটি খালি রেসপন্স পায়। এটি একটি বড় ধরনের ক্র্যাশের চেয়েও বেশি বিপজ্জনক কারণ আপনার মনিটরিং সিস্টেম এটি লক্ষ্য নাও করতে পারে। প্রসেসটি তখনও চলছে। পোর্টটি তখনও লিসেন করছে। তবুও প্রতিটি টুল কল থেকে কিছুই পাওয়া যাচ্ছে না।

AI মডেল নীরবতাকে ব্যর্থতা হিসেবে গণ্য করে না। এটি নীরবতাকে একটি সফল কল হিসেবে দেখে যা কোনো ডেটা তৈরি করেনি। সেই ব্ল্যাঙ্ক রেসপন্স মডেলটিকে অনুমান করতে বা ইমপ্রোভাইজ করতে বাধ্য করে। এটি শূন্যস্থান পূরণের জন্য কাল্পনিক তথ্য বা হ্যালুসিনেশন তৈরি করতে শুরু করে, অথবা একই ভুল কল বারবার করার লুপে পড়ে যায়। সাময়িক নেটওয়ার্ক টাইমআউট বা একটি ভুল টুল আর্গুমেন্টের মতো ছোট সমস্যাগুলোকে কখনোই এই ধরনের আচরণের কারণ হতে দেওয়া উচিত নয়।

দ্য র‍্যাপার প্যাটার্ন: ত্রয়ী প্রতিরক্ষা ব্যবস্থা

আমি প্রতিটি টুল হ্যান্ডলারকে একটি পাতলা এরর-রিকভারি লেয়ার দিয়ে র‍্যাপ (wrap) করে এটি সমাধান করেছি। র‍্যাপারটি প্রতিটি সম্ভাব্য ব্যর্থতা অনুমান করার চেষ্টা করে না। এটি সেগুলোকে শ্রেণীবদ্ধ করে এবং সেই অনুযায়ী প্রতিক্রিয়া জানায়।

ConnectionError এবং TimeoutError
এগুলো তখন ঘটে যখন আপনার সার্ভার একটি এক্সটার্নাল API-এর সাথে যোগাযোগ করার সময় নেটওয়ার্কের অস্থিরতা দেখা দেয়। সহজাত সমাধান হলো পুরো MCP সার্ভার প্রসেসটি রিস্টার্ট করা। তা করবেন না। রিবুট করলে সক্রিয় ক্লায়েন্ট কানেকশন বিচ্ছিন্ন হয়ে যায়, ইন-মেমরি স্টেট মুছে যায় এবং সম্পূর্ণ রিস্টার্ট করতে বাধ্য করে। পরিবর্তে, কানেকশন ফেইলরটি ধরুন এবং শুধুমাত্র আপনার টুলটি ব্যবহৃত ট্রান্সপোর্ট লেয়ার বা HTTP ক্লায়েন্টটি পুনরায় কানেক্ট করুন। এতে সার্ভারটি সচল থাকে এবং তাৎক্ষণিকভাবে পরবর্তী রিকোয়েস্টের জন্য প্রস্তুত থাকে।

ValueError
এটি তখন দেখা যায় যখন AI ক্লায়েন্ট ভুল বা ত্রুটিপূর্ণ (malformed) আর্গুমেন্ট পাঠায়। হতে পারে মডেলটি একটি নতুন প্যারামিটার উদ্ভাবন করেছে, যেখানে একটি ইন্টিজার প্রয়োজন ছিল সেখানে একটি স্ট্রিং পাঠিয়েছে, অথবা কোনো প্রয়োজনীয় ফিল্ড ভুলে গেছে। আপনি যদি এটিকে আনহ্যান্ডেলড অবস্থায় উপরে উঠতে দেন, তবে ক্লায়েন্ট হয় ক্র্যাশ পাবে অথবা একটি ব্ল্যাঙ্ক রিপ্লাই পাবে। র‍্যাপারের ভেতরে এটি ধরুন, তারপর একটি স্পষ্ট ও সুনির্দিষ্ট বার্তা তৈরি করুন যা মডেলকে ঠিক কী ভুল হয়েছে তা বলে দেবে। কোন প্যারামিটারটি ব্যর্থ হয়েছে এবং কী প্রত্যাশিত ছিল তা ব্যাখ্যা করুন। বেশিরভাগ আধুনিক AI মডেল সেই বার্তাটি পড়বে এবং পরবর্তী ধাপেই নিজেকে সংশোধন করে নেবে। একটি অস্পষ্ট এরর রিজনিং সাইকেল নষ্ট করে। একটি সুনির্দিষ্ট এরর তাৎক্ষণিকভাবে সমস্যাটি সমাধান করে।

General Exceptions
একটি সেফটি নেট রাখুন। যদি কোনো এরর উপরের ক্যাটাগরিগুলোর বাইরে হয়, তবে নিজের জন্য বিস্তারিত লগ করুন এবং ক্লায়েন্টকে একটি পরিষ্কার ও সাধারণ ফেইলর রেসপন্স পাঠান। এটি একটি অদ্ভুত এজ কেস (edge case) যেন সবার জন্য সেশনটি নষ্ট না করে দেয় তা নিশ্চিত করে। সার্ভারটি টিকে থাকে, ক্লায়েন্ট একটি সিগন্যাল পায় যে কিছু একটা ব্যর্থ হয়েছে, এবং আপনি পরে ডিবাগ করার জন্য আপনার লগে যথেষ্ট কনটেক্সট রাখতে পারেন।

isError ফ্ল্যাগটি অপরিহার্য

এখানে সেই বিষয়টি রয়েছে যা আসলে নির্ধারণ করে যে আপনার সমাধানটি কাজ করছে কি না। MCP রেসপন্সে একটি isError বুলিয়ান ফিল্ড থাকে। যদি কোনো এক্সেপশন ঘটে এবং আপনি isError-কে true সেট না করে একটি এরর মেসেজ রিটার্ন করেন, তবে ক্লায়েন্ট সেই এরর টেক্সটটিকে একটি সফল টুলের ফলাফল হিসেবে গণ্য করবে।

কল্পনা করুন আপনার এক্সটার্নাল API একটি রেট লিমিটে পৌঁছেছে। আপনি এক্সেপশনটি ধরলেন এবং "API rate limit exceeded" স্ট্রিংটি রিটার্ন করলেন কিন্তু isError-কে false রেখে দিলেন। ক্লায়েন্ট সেই স্ট্রিংটিকে মডেলের কনটেক্সট উইন্ডোতে এমনভাবে পাস করে যেন সেটি আসল টুলের আউটপুট। মডেলটি তখন সেই টেক্সটটিকে ডেটা হিসেবে ধরে নিয়ে বিশ্লেষণ করার চেষ্টা করে। এটি সামারিতে এররটি উদ্ধৃত করতে পারে, অথবা আরও খারাপভাবে, এটি সেই এরর টেক্সট এবং অন্যান্য তথ্যের মধ্যে কাল্পনিক সম্পর্ক তৈরি করতে পারে। আপনি একটি সাময়িক অবকাঠামোগত সমস্যাকে ভুল তথ্যের উৎসে পরিণত করেছেন।

যখনই আপনি একটি error payload রিটার্ন করবেন, সবসময় isError কে true সেট করুন। এটি ক্লায়েন্টকে একটি স্পষ্ট সংকেত দেয় যে টুল কলটি ব্যর্থ হয়েছে, যা মডেলকে সিদ্ধান্ত নিতে সাহায্য করে যে এটি পুনরায় চেষ্টা (retry) করবে কি না, স্পষ্টীকরণের জন্য জিজ্ঞাসা করবে কি না, নাকি সম্পূর্ণ ভিন্ন কোনো টুল ব্যবহার করবে।

কী ধরতে হবে (Catch) এবং কী বন্ধ করতে হবে (Kill) তা জানুন

আপনার পুরো সার্ভারকে এমন কোনো অন্ধ try-catch দিয়ে মুড়িয়ে দেবেন না যা সবকিছু গিলে ফেলে। কিছু ত্রুটির অর্থ হলো সার্ভারটিকে অবিলম্বে বন্ধ হয়ে যাওয়া উচিত। যদি স্টার্টআপের সময় কোনো প্রয়োজনীয় environment variable অনুপস্থিত থাকে, অথবা আপনার কনফিগারেশন ফাইলটি ক্ষতিগ্রস্ত (corrupt) হয়, তবে রিকোয়েস্ট-লেভেল ক্যাচিং কোনো কাজে আসবে না। এই ধরনের মারাত্মক (fatal) ত্রুটিগুলোর জন্য একটি নির্দিষ্ট exception class তৈরি করুন এবং সেগুলোকে প্রসেসটি ক্র্যাশ করতে দিন।

নিয়মটি সহজ। যদি ত্রুটিটি সাময়িক হয় বা শুধুমাত্র একটি রিকোয়েস্টের মধ্যে সীমাবদ্ধ থাকে, তবে সেটি ধরুন (catch) এবং রিকভার করুন। আর যদি ত্রুটিটির মানে হয় যে পরবর্তী প্রতিটি রিকোয়েস্ট ব্যর্থ হওয়া নিশ্চিত, তবে সার্ভারটিকে স্পষ্টভাবে বন্ধ হতে দিন। স্টার্টআপের সময় দ্রুত ব্যর্থ হওয়া অনেক ভালো, সেই সার্ভারের চেয়ে যা ত্রুটিপূর্ণ অবস্থায় দিনের পর দিন কোনোমতে চলতে থাকে।

প্রয়োজন হওয়ার আগেই Observability যোগ করুন

একবার যখন আপনার wrapper তৈরি হয়ে যাবে, তখন এটিকে structured logging-এর সাথে যুক্ত করুন। প্রতিটি টুল কল এবং এর ফলাফল JSON ফরম্যাটে লগ করুন। এতে টুলের নাম, raw arguments, latency এবং এটি সফল হয়েছে, ব্যর্থ হয়েছে নাকি পুনরায় চেষ্টা (retry) করা হয়েছে তা অন্তর্ভুক্ত করুন।

এই শৃঙ্খলা দ্রুত সুফল দেয়। যখন আপনি ত্রুটির সংখ্যা বাড়তে দেখবেন, তখন আপনি টুল অনুযায়ী ফিল্টার করতে পারবেন এবং কয়েক মিনিটের মধ্যেই প্যাটার্ন শনাক্ত করতে পারবেন। হতে পারে একটি নির্দিষ্ট এক্সটার্নাল API প্রতিদিন একই সময়ে timeout দিচ্ছে, যা কোনো একটি শিডিউল করা মেইনটেন্যান্স উইন্ডোর দিকে ইঙ্গিত করছে যা আপনি জানতেন না। হতে পারে একটি টুল ক্রমাগত ভুলভাবে গঠিত (malformed) arguments পাচ্ছে, যা আপস্ট্রিম কোনো prompt engineering ত্রুটি প্রকাশ করছে। স্ট্যাক ট্রেসের (stack traces) মধ্যে হারিয়ে যাওয়া plain text লগ এই গোয়েন্দাগিরিকে কষ্টসাধ্য করে তোলে। Structured JSON এটিকে অত্যন্ত সহজ করে দেয়।

প্রোডাকশন ফলাফল

আমি গত তিন সপ্তাহ ধরে দুটি প্রোডাকশন MCP সার্ভারে এই wrapper প্যাটার্নটি ব্যবহার করছি। এই সময়ের মধ্যে আমি একটিও silent failure বা নিঃশব্দ ব্যর্থতা দেখিনি। এই wrapper যোগ করার আগে, প্রতিদিন গড়ে প্রায় একটি করে ব্যাখ্যাতীত ব্যর্থতা ঘটত। প্যাটার্নটি জটিল নয়, কিন্তু এর প্রভাব বিশাল কারণ এটি সাময়িক সমস্যা (noise) এবং প্রকৃত সমস্যাকে আলাদা করে দেয়।

Silent failure ক্র্যাশ করার চেয়ে বেশি ক্ষতিকর। একটি ক্র্যাশ আপনার অ্যালার্টিং সিস্টেমকে সক্রিয় করে তোলে। কিন্তু নীরবতা কেবল বিশ্বাস নষ্ট করে। একদিন আপনার AI এজেন্ট প্রয়োজনীয় টুল ডেটা প্রদান করছে, আর পরের দিন সেটি উল্টোপাল্টা তথ্য দিতে শুরু করছে কারণ সার্ভারটি কয়েক ঘণ্টা আগেই উত্তর দেওয়া বন্ধ করে দিয়েছে। Wrapper প্যাটার্ন এই ব্যবধান কমিয়ে দেয়। এটি ছোটখাটো সমস্যা সত্ত্বেও আপনার সার্ভারকে সচল রাখে, মডেলকে তার নিজের ভুল সংশোধন করার জন্য যথেষ্ট কনটেক্সট দেয় এবং নিশ্চিত করে যে যখন সত্যিই কোনো মারাত্মক কিছু ঘটে, আপনি তা সাথে সাথে জানতে পারেন।

আপনি যদি আজ MCP টুল তৈরি করেন, তবে wrapper এবং isError ফ্ল্যাগ দিয়ে শুরু করুন। বাকি সবকিছু কেবল পরিমার্জন বা cleanup মাত্র।