LLM yang bisa menulis SQL ke database produksi adalah risiko. Cepat atau lambat ia akan menulis DELETE, atau join yang memindai satu miliar baris, atau query ke tabel yang seharusnya tidak ia lihat. Instruksi di prompt tidak mengubah itu, karena model tidak punya cara menegakkannya.
Alternatifnya adalah membiarkan model bertanya tapi tidak pernah menulis query. Server MCP yang menerjemahkan, dan model hanya melihat jawabannya.
Tool, bukan kotak query
Setiap kemampuan adalah tool bernama dengan daftar parameter bertipe: pendapatan bulanan per lini produk, jumlah pesanan per wilayah, pergerakan stok satu SKU. Model memilih tool dan mengisi parameter. Ia tidak bisa menyusun SQL sembarang, jadi jangkauan kerusakan dari model yang bingung adalah satu parameter yang salah bentuk.
Parameternya di-bind, bukan disisipkan. Tool yang menerima rentang tanggal menerima dua tanggal dan query-nya memakai placeholder. Tidak ada penggabungan string di sepanjang jalur itu, yang menghapus permukaan injeksi alih-alih menyaringnya.
#[tool(name = "revenue_by_product_line")]
async fn revenue_by_product_line(
&self,
from: NaiveDate,
to: NaiveDate,
) -> Result<Json<RevenueReport>> {
sqlx::query_as::<_, RevenueRow>(REVENUE_BY_LINE)
.bind(from)
.bind(to)
.fetch_all(&self.pool)
.await
.map(Into::into)
.map_err(Into::into)
}
Data yang sudah diizinkan, nol kepercayaan pada klien
Separuh masalah berikutnya adalah cakupan. Tool seharusnya mengembalikan blok data yang boleh dilihat pemanggil, dihitung oleh server, bukan tabel mentah yang lalu disaring model. Artinya agregasi dan batas baris hidup di dalam tool, dan responsnya sudah aman saat keluar.
Inilah bagian yang mudah salah di demo. Mengembalikan tabel penuh lalu meminta model merangkumnya berjalan sampai ada yang bertanya sesuatu yang dijawab model dari kolom yang seharusnya tidak ia pegang.
Dua transport, satu binary
Server ini berbicara STDIO dan HTTP dari binary yang sama. STDIO dipakai klien desktop saat ia menjalankan prosesnya; HTTP dipakai deployment bersama. Menjaga keduanya dalam satu build berarti definisi tool tidak bisa berbeda antara jalur lokal dan jalur hosted.
Pelajaran yang layak dibawa adalah batasnya. Model bagus untuk memutuskan pertanyaan mana yang diajukan. Ia bukan komponen yang tepat untuk memutuskan bagaimana pertanyaan itu mencapai database.



