Kepercayaan dimulai dari alur yang dapat diikuti
Saya membuat MCP Query bukan karena kekurangan tool database. Masalahnya, saya tidak dapat menjelaskan apa yang dilakukan beberapa server setelah menerima tool call.
Untuk tool yang dapat mengakses data, “berhasil” belum cukup. Saya ingin mengetahui lokasi validasi input, eksekusi query, error yang melewati boundary, dan response yang diterima client.
Saya menyederhanakan pertanyaan tersebut menjadi satu alur:
Setiap layer harus memiliki alasan. Jika sebuah layer tidak dapat menjelaskan kompleksitas yang ditanganinya, kemungkinan ia hanya menambah indirection.
Memisahkan protocol dan database
MCP tool menjelaskan capability kepada client dan menerima input terstruktur. Ia tidak seharusnya sekaligus menangani connection pool, konversi driver, dan percabangan setiap engine.
Core handler menangani operasi yang menghadap protocol:
- Mem-parsing dan memvalidasi input.
- Memanggil operasi yang dipilih.
- Menormalisasi hasil yang berhasil.
- Mengklasifikasikan failure yang dikenal.
- Menahan exception yang tidak terduga.
Adapter menangani sumber data. PostgreSQL dan SQLite dapat menyediakan capability query yang sama tanpa berpura-pura bahwa koneksi, value, dan failure keduanya identik.
Pemisahan ini membuat public boundary dapat diuji tanpa menyembunyikan perilaku engine yang penting di balik mock.
Kategori error adalah bagian dari contract
Mengembalikan query failed untuk semua masalah memudahkan server, tetapi tidak membantu client. Mengirim exception driver mentah dapat membocorkan detail koneksi dan tetap tidak menyediakan action yang stabil.
Saya menjaga kategori publik tetap kecil:
invalid_input: argumen tool tidak sesuai schema.connection_failed: adapter tidak dapat mencapai sumber data.query_rejected: operasi melanggar aturan server.query_failed: engine menolak request yang valid.internal_error: failure tak terduga mencapai boundary.
Internal log dapat menyimpan exception asli dan correlation data. Response MCP hanya membawa pesan aman, kategori stabil, dan context yang dibutuhkan client untuk memilih langkah berikutnya.
Pemisahan ini penting karena observability dan public error menyelesaikan masalah yang berbeda.
Hasil stabil lebih berguna daripada hasil mentah
Setiap driver mengembalikan metadata dan tipe value yang berbeda. Jika response driver diteruskan langsung, contract MCP akan bergantung pada engine yang dipilih.
Adapter mengubah output engine menjadi hasil bersama yang kecil:
type QueryResult = {
columns: string[];
rows: Record<string, unknown>[];
rowCount: number;
durationMs: number;
};
Bentuk bersama tidak boleh menghapus informasi yang dibutuhkan client. Ia hanya menghilangkan perbedaan incidental yang dapat membocorkan implementasi driver ke setiap caller.
Large value, date, binary field, dan tipe numeric khusus engine tetap membutuhkan aturan konversi. “Dapat diubah menjadi JSON” adalah requirement, bukan kebijakan tipe yang lengkap.
Menguji boundary dengan engine nyata
Unit test dapat memastikan input salah tidak mencapai adapter dan exception yang dikenal berubah menjadi kategori publik yang tepat.
Unit test tidak dapat membuktikan konversi value SQL Server sama dengan PostgreSQL atau bentuk connection error sesuai asumsi adapter.
Karena itu, workspace memakai Docker Compose untuk integration test. Setiap adapter dijalankan terhadap service lokal yang nyata, sedangkan SQLite dan Excel memakai file lokal.
Test matrix pada boundary mencakup:
- Query valid mengembalikan bentuk hasil bersama.
- Input kosong atau salah gagal sebelum query dijalankan.
- Connection error menjadi
connection_failed. - SQL salah tetap berbeda dari input tool yang salah.
- Detail engine tidak membocorkan credential.
- Hasil besar mengikuti batas yang eksplisit.
Mock tetap berguna, tetapi bukan bukti bahwa beberapa database engine memiliki perilaku sama.
Hal yang membuat server lebih mudah dipahami
Perbaikan terbesar bukan menambah abstraction. Perbaikannya adalah memberi setiap failure dan konversi satu pemilik yang jelas.
Protocol concern berada di handler. Engine concern berada di adapter. Runtime log menyimpan detail operasional. Response MCP menjaga public contract tetap stabil.
Dengan boundary tersebut, saya dapat menelusuri satu request tanpa membaca seluruh repository. Itu menjadi standar saya sebelum memberi AI client akses ke tool.