Server MCP saya dulu sering tiba-tiba berhenti bekerja. Tanpa crash dump. Tanpa stack trace di log. Klien terhubung tanpa keluhan, lalu setelah beberapa jam, semuanya menjadi sunyi. Permintaan menghilang dan agen AI di ujung lainnya tidak menerima apa pun selain kekosongan.

Ini adalah cerita yang sangat umum dan membuat frustrasi dalam ekosistem Model Context Protocol (MCP). Protokol ini mendefinisikan bagaimana agen AI menemukan dan memanggil alat eksternal, tetapi spesifikasinya mengasumsikan Anda akan menangani kesalahan sendiri. Kebanyakan tutorial dan implementasi awal melewatkan bagian itu. Mereka fokus pada jalur sukses (happy path): memberi anotasi pada fungsi, mengeksposnya melalui server, dan mengembalikan hasil yang bersih. Mereka jarang menunjukkan apa yang terjadi ketika gangguan jaringan menghantam API eksternal Anda, atau ketika model berhalusinasi tentang nama parameter dan mengirimkan input sampah. Hasilnya adalah server rapuh yang terlihat sehat padahal sebenarnya sudah mati selama berjam-jam.

Mengapa Respons Kosong Lebih Buruk daripada Crash

Ketika pengecualian (exception) yang tidak tertangani lolos dalam handler alat MCP, lapisan transport sering kali menelannya. Proses server tetap hidup, socket tetap terbuka, tetapi klien mendapatkan respons kosong. Ini lebih berbahaya daripada crash yang terlihat jelas karena pemantauan Anda mungkin tidak menyadarinya. Prosesnya masih berjalan. Port-nya masih mendengarkan. Namun, setiap panggilan alat tidak mengembalikan apa pun.

Model AI tidak menafsirkan keheningan sebagai kegagalan. Ia menafsirkan keheningan sebagai panggilan sukses yang tidak menghasilkan data. Respons kosong tersebut melatih model untuk berimprovisasi. Ia mulai berhalusinasi tentang fakta untuk mengisi kekosongan, atau terjebak dalam loop mencoba kembali panggilan yang rusak tersebut. Masalah kecil seperti timeout jaringan sementara atau argumen alat yang tidak valid tidak boleh dibiarkan menyebabkan perilaku semacam ini.

Pola Wrapper: Tiga Lapis Pertahanan

Saya memperbaiki ini dengan membungkus setiap tool handler dalam lapisan pemulihan kesalahan yang tipis. Wrapper tersebut tidak mencoba memprediksi setiap kegagalan yang mungkin terjadi. Ia mengategorikannya dan merespons dengan semestinya.

ConnectionError dan TimeoutError
Ini muncul ketika server Anda berkomunikasi dengan API eksternal dan jaringan tidak stabil. Perbaikan instingtifnya adalah dengan memulai ulang seluruh proses server MCP. Jangan lakukan itu. Melakukan reboot akan memutuskan koneksi klien yang aktif, menghapus status dalam memori, dan memaksa inisialisasi ulang secara penuh. Sebaliknya, tangkap kegagalan koneksi dan hubungkan kembali hanya lapisan transport atau klien HTTP yang digunakan alat Anda. Server akan tetap "hangat" dan siap untuk permintaan berikutnya secara instan.

ValueError
Inilah yang Anda lihat ketika klien AI mengirimkan argumen yang salah format. Mungkin model mengarang parameter, mengirimkan string padahal yang dibutuhkan adalah integer, atau melupakan kolom yang wajib diisi. Jika Anda membiarkan ini naik ke atas tanpa ditangani, klien akan mendapatkan crash atau balasan kosong. Tangkap kesalahan ini di dalam wrapper, lalu buatlah pesan yang jelas dan spesifik yang memberi tahu model dengan tepat apa yang salah. Jelaskan parameter mana yang gagal dan apa yang diharapkan. Sebagian besar model AI modern akan membaca pesan tersebut dan melakukan koreksi mandiri pada giliran berikutnya. Kesalahan yang samar membuang-buang siklus penalaran. Kesalahan yang presisi memperbaiki masalah secara instan.

General Exceptions
Siapkan jaring pengaman. Jika kesalahan berada di luar kategori di atas, catat detailnya untuk Anda sendiri dan kembalikan respons kegagalan generik yang bersih ke klien. Ini mencegah satu kasus ekstrem (edge case) yang aneh merusak sesi bagi semua orang. Server tetap bertahan, klien mendapatkan sinyal bahwa sesuatu telah gagal, dan Anda menyimpan cukup konteks dalam log untuk proses debug nantinya.

Flag isError Tidak Bisa Ditawar

Inilah detail yang sebenarnya menentukan apakah perbaikan Anda berhasil. Respons MCP menyertakan bidang boolean isError. Jika terjadi pengecualian dan Anda mengembalikan pesan kesalahan tanpa mengatur isError menjadi true, klien akan menganggap teks kesalahan tersebut sebagai hasil alat yang sukses.

Bayangkan API eksternal Anda mencapai batas kuota (rate limit). Anda menangkap pengecualian tersebut dan mengembalikan string "API rate limit exceeded" tetapi membiarkan isError bernilai false. Klien memasukkan string tersebut ke dalam jendela konteks model seolah-olah itu adalah output alat yang nyata. Model kemudian mencoba menalar teks tersebut seolah-olah itu adalah data. Ia mungkin mengutip kesalahan tersebut dalam ringkasan, atau lebih buruk lagi, ia mungkin berhalusinasi tentang hubungan antara teks kesalahan tersebut dengan fakta lainnya. Anda telah mengubah gangguan infrastruktur sementara menjadi sumber misinformasi.

Selalu atur isError ke true saat Anda mengembalikan payload kesalahan. Ini memberikan sinyal yang jelas kepada klien bahwa pemanggilan alat (tool call) gagal, yang memungkinkan model memutuskan apakah akan mencoba lagi, meminta klarifikasi, atau mencoba alat yang berbeda sama sekali.

Ketahui Apa yang Harus Ditangkap dan Apa yang Harus Dihentikan

Jangan membungkus seluruh server Anda dalam try-catch buta yang menelan segalanya. Beberapa kesalahan berarti server harus segera berhenti. Jika variabel lingkungan (environment variable) yang diperlukan hilang saat startup, atau file konfigurasi Anda rusak, tidak ada penanganan di tingkat permintaan (request-level catching) yang akan membantu. Buatlah kelas pengecualian (exception class) khusus untuk kesalahan fatal seperti ini dan biarkan proses tersebut crash.

Aturannya sederhana. Jika kesalahan bersifat sementara atau terisolasi pada satu permintaan saja, tangkap dan pulihkan. Jika kesalahan tersebut berarti setiap permintaan berikutnya dijamin akan gagal, biarkan server mati dengan jelas. Kegagalan cepat saat startup jauh lebih baik daripada server yang berjalan tersendat-sendat selama berhari-hari dalam kondisi rusak.

Tambahkan Observabilitas Sebelum Anda Membutuhkannya

Setelah wrapper terpasang, pasangkan dengan logging terstruktur. Catat setiap pemanggilan alat dan hasilnya dalam format JSON. Sertakan nama alat, argumen mentah, latensi, dan apakah berhasil, gagal, atau dicoba kembali.

Disiplin ini akan membuahkan hasil dengan cepat. Saat Anda menyadari adanya lonjakan kesalahan, Anda dapat memfilter berdasarkan alat dan menemukan pola dalam hitungan menit. Mungkin API eksternal tertentu mulai mengalami timeout pada waktu yang sama setiap hari, yang menunjukkan adanya jendela pemeliharaan terjadwal yang tidak Anda ketahui. Mungkin satu alat menerima argumen yang terus-menerus salah format, mengungkapkan adanya cacat prompt engineering di bagian hulu. Log teks biasa yang terkubur dalam stack trace membuat pekerjaan detektif ini menyakitkan. JSON terstruktur membuatnya sangat mudah.

Hasil di Produksi

Saya telah menjalankan pola wrapper ini pada dua server MCP produksi selama tiga minggu terakhir. Dalam jangka waktu tersebut, saya tidak melihat adanya kegagalan senyap (silent failures) sama sekali. Sebelum menambahkan wrapper, saya rata-rata mengalami sekitar satu kegagalan yang tidak dapat dijelaskan setiap hari. Polanya tidak rumit, tetapi dampaknya sangat besar karena ia memisahkan gangguan (noise) yang bisa diatasi dari masalah yang sebenarnya.

Kegagalan senyap lebih mahal harganya daripada crash. Crash akan memicu sistem peringatan (alerting system) Anda. Keheningan hanya akan mengikis kepercayaan. Suatu hari agen AI Anda mengembalikan data alat yang berguna, dan keesokan harinya ia mulai mengarang informasi karena server sudah berhenti menjawab beberapa jam sebelumnya. Pola wrapper menutup celah tersebut. Ia menjaga server Anda tetap berjalan melewati turbulensi kecil, memberi model konteks yang cukup untuk memperbaiki kesalahannya sendiri, dan memastikan bahwa ketika sesuatu yang benar-benar fatal terjadi, Anda segera mengetahuinya.

Jika Anda sedang membangun alat MCP saat ini, mulailah dengan wrapper dan flag isError. Sisanya hanyalah pembersihan.