Ringkasan Utama
Agar AI agent tidak bisa menjalankan helm uninstall lewat Kubernetes MCP server, terapkan read-only di cluster dengan ServiceAccount berperan view, lalu atur read_only, daftar toolsets minimal dan denied_resources di TOML. Verifikasi dengan tools/list: di v0.0.67, read_only menampilkan 14 dari 22 tool, sedangkan salah ketik key menampilkan 20.
Saya ingin sebuah agent menjawab kenapa sebuah pod crash-loop, dan saya sadar server yang sama juga menawarkan helm_uninstall. Servernya adalah containers/kubernetes-mcp-server, program Go yang bicara langsung ke API server, bukan memanggil kubectl atau helm. Instalasi bawaannya berupa sebuah credential plus daftar tool, dan daftar itu lebih panjang dari pekerjaannya.
Tulisan ini adalah konfigurasi aman yang saya susun hanya dari dokumentasi proyeknya sendiri dan dari audit tools/list yang saya jalankan terhadap v0.0.67, rilis 18 September 2026. Saya tidak memanggil tool apa pun dan tidak menyentuh cluster sungguhan: audit memakai kubeconfig tiruan, jadi yang saya ukur adalah apa yang ditawarkan server ke agent, bukan perilaku filter di bawah beban.
Daftar tool adalah blast radius-nya
Dengan toolset core dan helm aktif, v0.0.67 menawarkan 22 tool lewat stdio. Empat belas di antaranya punya readOnlyHint=true. Delapan lainnya bisa mengubah cluster, dan itulah seluruh permukaan risiko server ini. Anotasi di bawah adalah yang dilaporkan server sendiri, sedangkan catatannya merangkum README upstream.
| Tool | readOnlyHint | destructiveHint | Fungsinya |
|---|---|---|---|
| helm_install | false | false | Memasang chart. Referensi oci atau https apa pun diterima kecuali allowed_registries diatur. |
| helm_uninstall | false | true | Menghapus sebuah Helm release. |
| pods_delete | false | true | Menghapus Pod berdasarkan nama. |
| pods_exec | false | true | Menjalankan perintah di dalam container. |
| pods_run | false | false | Menjalankan Pod dari image container apa pun. |
| resources_create_or_update | false | true | Server-Side Apply untuk manifest lengkap, kind apa pun. |
| resources_delete | false | true | Menghapus resource apa pun berdasarkan apiVersion, kind dan nama. |
| resources_scale | false | true | Membaca atau mengubah jumlah replica. |
Dua baris perlu dilihat lagi. helm_install dan pods_run adalah tool write yang tidak diberi label destructive, jadi aturan berbasis destructiveHint meloloskan keduanya. Lalu resources_create_or_update menerima manifest lengkap untuk kind apa pun, sehingga ia tool write serbaguna, bukan sekadar pembantu pod. README menyebut manifest adalah desired state yang utuh, jadi field yang dihilangkan ikut terhapus.
Tujuh lapisan, dan hanya satu yang batas sungguhan
Server ini menyediakan beberapa cara untuk menolak. Bobotnya tidak sama. Satu ditegakkan oleh Kubernetes sendiri, lima ditegakkan oleh MCP server di depan credential Anda, dan satu bergantung pada manusia yang bisa dihubungi.
| Lapisan | Setting | Yang dihentikan | Ditegakkan oleh |
|---|---|---|---|
| ServiceAccount RBAC | view ClusterRole plus token berumur pendek | Verb apa pun yang tidak dimiliki identity, apa pun yang diminta agent | Kubernetes API server |
| Mode read-only | read_only | Setiap tool yang tidak diberi anotasi readOnlyHint=true | MCP server, dari anotasi tool |
| Tanpa tool destructive | disable_destructive | Tool dengan anotasi destructiveHint=true | MCP server, dari anotasi tool |
| Allowlist atau denylist tool | enabled_tools, disabled_tools | Tool yang disebut namanya, berlabel atau tidak | MCP server |
| Denylist resource | denied_resources | Group, version dan kind tertentu seperti Secret | MCP server |
| Konfirmasi manusia | confirmation_rules | Panggilan yang cocok dengan tool atau verb dan kind Kubernetes, sampai seseorang setuju | MCP client; confirmation_fallback menentukan saat client tidak bisa bertanya |
| Sumber chart | toolset_configs.helm allowed_registries | helm_install menarik dari registry yang tidak terdaftar | Toolset helm |
Spesifikasi Model Context Protocol tegas soal lapisan tengah: client harus menganggap anotasi tool tidak tepercaya kecuali berasal dari server tepercaya, dan harus selalu ada manusia dalam loop yang bisa menolak panggilan. Di sini servernya milik Anda, jadi anotasinya cukup bisa dipercaya, tetapi read_only hanya sebenar label tiap tool. pods_run yang tidak berlabel membuktikan bahwa label adalah kebijakan yang ditulis orang, bukan batas fisik.
Dokumen tool RBAC metadata milik proyek mengatakan hal yang sama dari sisi lain: metadata hanyalah alat bantu perencanaan, tidak memberi akses, dan Kubernetes API server tetap yang berwenang. Metadata itu dipublikasikan per tool di bawah key io.kubernetes-mcp-server/rbac, dan contoh deklarasi unbounded-nya adalah tool Helm yang izinnya bergantung pada resource di dalam chart. Itulah ringkasan jujur helm_install: tidak ada filter yang bisa membatasi apa yang dibuat chart sembarang, jadi credential-lah yang harus membatasinya.
Mulai dari ServiceAccount yang tidak bisa menulis
Panduan getting-started upstream menyarankan identity read-only khusus, dan ini satu-satunya lapisan yang tidak akan saya lewati. Panduan itu mengikat ClusterRole view bawaan ke sebuah ServiceAccount dan membuat token berbatas waktu dengan TokenRequest API. Jalankan pengecekan can-i sebelum agent pernah melihat kubeconfig-nya.
# From the upstream getting-started guide: a read-only identity for the agent.
kubectl create namespace mcp
kubectl create serviceaccount mcp-viewer -n mcp
# The built-in "view" ClusterRole: read most objects, NOT Secrets, NOT roles.
kubectl create clusterrolebinding mcp-viewer-crb \
--clusterrole=view \
--serviceaccount=mcp:mcp-viewer
# Short-lived token (TokenRequest API, Kubernetes 1.24+). The guide uses 2 hours.
TOKEN="$(kubectl create token mcp-viewer --duration=2h -n mcp)"
# Prove the boundary BEFORE the agent ever sees it. These are the checks that matter:
kubectl auth can-i list pods --as=system:serviceaccount:mcp:mcp-viewer --all-namespaces # yes
kubectl auth can-i delete pods --as=system:serviceaccount:mcp:mcp-viewer --all-namespaces # no
kubectl auth can-i get secrets --as=system:serviceaccount:mcp:mcp-viewer --all-namespaces # no
kubectl auth can-i create pods --subresource=exec \
--as=system:serviceaccount:mcp:mcp-viewer -n default # noDokumentasi RBAC Kubernetes menjelaskan view sebagai akses baca ke sebagian besar objek dalam satu namespace yang tidak mengizinkan melihat role atau role binding, dan menyatakan role ini tidak boleh membaca Secret, karena membacanya memberi akses sebagai ServiceAccount mana pun di namespace itu. Satu konsekuensi yang saya perkirakan tetapi belum saya uji di cluster sungguhan: toolset helm mendokumentasikan secret sebagai storage driver bawaan, dan state release Helm disimpan di Secret, jadi helm_list mungkin butuh izin tambahan atau storage_driver diganti ke configmap. Uji dulu sebelum menjanjikan helm view ke agent.
File konfigurasi yang akan saya pakai
Setting runtime ada di TOML, dan dokumentasi menyarankan file drop-in lewat --config-dir agar kebijakan dasar dan override terpisah. File dasar di bawah sengaja membosankan: read-only, dua toolset, dua kind yang ditolak, dan instruksi yang memberi tahu model untuk apa server ini ada.
# /etc/kubernetes-mcp-server/conf.d/00-base.toml
# Start: kubernetes-mcp-server --config-dir /etc/kubernetes-mcp-server/conf.d
# Only tools annotated readOnlyHint=true are exposed. On v0.0.67 with core + helm
# this left 14 of 22 tools (measured with tools/list, see the verification section).
read_only = true
# Load only what the job needs. The default is ["core", "config"]; helm, kiali,
# kubevirt, tekton and the observability sets are opt-in.
toolsets = ["core", "helm"]
# Belt and braces: even a credential broader than "view" cannot read these kinds.
[[denied_resources]]
group = ""
version = "v1"
kind = "Secret"
[[denied_resources]]
group = "rbac.authorization.k8s.io"
version = "v1"
kind = "ClusterRoleBinding"
# Tell the model what this server is for, so it does not reach for write tools.
server_instructions = """
Read-only Kubernetes diagnostics. List and inspect workloads, read logs and events.
Never attempt changes; propose a kubectl or helm command for a human instead.
"""denied_resources adalah pertahanan untuk hari ketika seseorang tidak sengaja mengikat role yang lebih luas ke agent. Dengan role view ia redundan untuk Secret, dan justru itu alasannya layak ada: ia tetap aman saat credential bergeser. server_instructions hanya saran, bukan penegakan, jadi ia tidak pernah menggantikan lapisan di atasnya.
Pakai drop-in bernomor dan SIGHUP. Dokumentasi mencantumkan read_only, disable_destructive, denied_resources, toolsets, filter tool dan confirmation rules sebagai setting yang bisa di-reload, jadi memberi atau mencabut akses write tidak perlu restart. Port, bind address dan kubeconfig tetap butuh restart, dan reload yang gagal validasi membuat proses keluar.
disable_destructive bukan read-only
Saat insiden butuh write, setting yang menggoda adalah disable_destructive karena terdengar seperti subset aman. Saya mengukur subset itu. Di v0.0.67 ia menyisakan 16 tool: 14 yang read-only ditambah helm_install dan pods_run. Keduanya tidak berlabel destructive, dan dengan keduanya agent bisa menjalankan image apa pun atau memasang chart apa pun yang diizinkan credential. Drop-in di bawah menutup kedua celah itu dan membuat konfirmasi yang tidak terjawab menjadi penolakan.
# /etc/kubernetes-mcp-server/conf.d/90-incident.toml (drop in only for an incident)
read_only = false
disable_destructive = true # hides tools with destructiveHint=true ...
# ... but helm_install and pods_run stay: neither is flagged.
# Closing the two gaps disable_destructive leaves open:
disabled_tools = ["pods_run"]
[toolset_configs.helm]
allowed_registries = ["oci://ghcr.io/myorg"] # helm_install may only pull from here
storage_driver = "secret" # the documented default
# Anything that still reaches a human prompt. If the client cannot ask, DENY.
confirmation_fallback = "deny"
[[confirmation_rules]]
destructive = true
message = "Destructive operation."
[[confirmation_rules]]
verb = "get"
kind = "Secret"
message = "Accessing a Secret."Dokumentasi menetapkan confirmation_fallback bawaan allow, artinya client yang tidak bisa menampilkan prompt akan lanjut diam-diam dengan peringatan di log saja. Untuk cluster produksi bawaan itu terbalik, jadi atur deny. Daftar allowed_registries adalah prefix match pada referensi chart berbasis URL. Saat diatur, nama gaya repo dan path lokal ditolak, dan referensi file serta http polos selalu diblokir.
Opsi instalasi di README adalah npx -y kubernetes-mcp-server@latest, uvx, binary native, atau image quay.io/containers/kubernetes_mcp_server. Ganti latest dengan versi persis seperti 0.0.67 untuk apa pun yang menjaga cluster, lalu baca release notes sebelum menaikkan versi.
Kegagalannya: typo yang fail open
Saya sengaja menguji kesalahan paling murah: file konfigurasi berisi readonly = true, bukan read_only = true. v0.0.67 berjalan tanpa keluhan dan menawarkan 20 tool dengan toolset core dan config bawaan, termasuk pods_delete, pods_exec dan resources_delete. Dengan key yang benar dan core plus helm aktif, server yang sama menawarkan 14 tool, semuanya read-only. Tidak ada keluaran proses yang memberi tahu bahwa file pertama tidak berpengaruh apa pun.
# Wrong: one missing underscore. On v0.0.67 this loaded without complaint and the
# server exposed 20 tools (default core + config sets), incl. pods_delete and pods_exec.
readonly = true
# Right: the key is read_only. With core + helm enabled it exposed 14 read-only tools.
read_only = true
# Wrong: a flag copied from an older tutorial. Current main documents that
# runtime flags are gone and a leftover one fails with cobra's "unknown flag".
# kubernetes-mcp-server --read-only --toolsets core,helm
# Right:
# kubernetes-mcp-server --config /etc/kubernetes-mcp-server/config.tomlJangan jadikan file konfigurasi sebagai tesnya. Key yang salah eja bukan error di versi yang saya jalankan, jadi satu-satunya bukti bahwa read_only aktif adalah daftar tool yang benar-benar diterima client. Audit tools/list di CI dan di setiap upgrade.
Dokumentasi di branch main menyatakan perilaku sebaliknya kini dimaksudkan: key TOML yang tidak dikenal menggagalkan load, dan flag runtime seperti --read-only, --toolsets dan --port dihapus demi TOML, dengan flag sisa gagal sebagai unknown. Di v0.0.67 saya menemukan flag lama --read-only masih bekerja dan menampilkan 14 tool. Jadi tutorial yang disalin dari era mana pun bisa rusak ke arah berbeda, dan versi yang Anda pin yang menentukan. Halaman yang sama mencatat setting sts 0.0.66 diganti tabel token_exchange, dan release notes 0.0.67 mencantumkan penghapusan transport SSE sebagai breaking change.
Buktikan: tanya server apa yang ia tawarkan
Audit yang menangkap typo tadi hanya tiga puluh baris Node. Ia menjalankan server lewat stdio, mengirim initialize dan tools/list, lalu mencetak tiap tool beserta anotasinya. Arahkan ke konfigurasi persis yang Anda deploy.
// list-tools.mjs - node list-tools.mjs /etc/kubernetes-mcp-server/config.toml
// Starts the server over stdio, asks tools/list, prints what an agent would see.
import { spawn } from "node:child_process";
const config = process.argv[2];
const server = spawn(
"npx",
["-y", "kubernetes-mcp-server@0.0.67", "--config", config],
{ stdio: ["pipe", "pipe", "inherit"] },
);
const send = (m) => server.stdin.write(JSON.stringify(m) + "\n");
let buffer = "";
server.stdout.on("data", (chunk) => {
buffer += chunk;
for (let i; (i = buffer.indexOf("\n")) >= 0; ) {
const line = buffer.slice(0, i);
buffer = buffer.slice(i + 1);
const msg = JSON.parse(line);
if (msg.id !== 2) continue;
for (const tool of msg.result.tools) {
const a = tool.annotations ?? {};
console.log(tool.name.padEnd(28), "readOnly:", a.readOnlyHint, "destructive:", a.destructiveHint);
}
server.kill();
}
});
send({ jsonrpc: "2.0", id: 1, method: "initialize",
params: { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "audit", version: "1" } } });
setTimeout(() => {
send({ jsonrpc: "2.0", method: "notifications/initialized" });
send({ jsonrpc: "2.0", id: 2, method: "tools/list" });
}, 3000);
// CI gate idea: fail the job if any listed tool has readOnly !== true.Tiga run saya terhadap kubeconfig tiruan, semuanya dengan toolsets core dan helm: tanpa pembatasan 22 tool dengan 8 yang tidak read-only, read_only 14, dan disable_destructive 16. Saya tidak menguji denied_resources, confirmation_rules maupun allowed_registries, jadi untuk ketiganya saya bersandar pada dokumentasi, dan Anda sebaiknya mengujinya di cluster percobaan sebelum mempercayainya. Jadikan skrip ini gate dengan gagal bila ada tool yang readOnlyHint-nya bukan true.
Yang dikerjakan hari Senin
Berurutan, dan tiap langkah bisa dicek sendiri:
- Pin versinya. Ganti @latest dengan rilis persis dan baca notes-nya saat menaikkan versi: 0.0.67 menghapus transport SSE dan mengubah konfigurasi token exchange.
- Buat ServiceAccount mcp-viewer dengan role view dan jalankan empat pengecekan can-i, dengan harapan yes untuk list pods dan no untuk delete pods, get secrets dan exec.
- Pasang drop-in dasar: read_only, daftar toolsets terkecil yang cukup, dan denied_resources untuk Secret dan ClusterRoleBinding.
- Jalankan audit tools/list di CI terhadap file itu dan gagalkan jika ada tool yang readOnlyHint-nya bukan true.
- Perlakukan akses write sebagai drop-in berbatas waktu: disable_destructive, disabled_tools untuk pods_run, allowed_registries, confirmation_fallback deny. Hapus dan kirim SIGHUP saat insiden selesai.
- Jika server berjalan di pod, atur port di TOML untuk mode HTTP dan ingat bind address bawaannya 0.0.0.0, yang diperingatkan server bila tanpa TLS atau OAuth. Bind ke 127.0.0.1 di belakang proxy, atau aktifkan require_oauth. configuration_view nonaktif secara bawaan di mode HTTP karena membuka isi kubeconfig.
Semua ini tidak membuat agent jadi pintar. Ia membuat skenario terburuk menjadi membosankan: model yang memutuskan menghapus sebuah release bertemu credential yang tidak bisa, dan daftar tool yang tidak menawarkan opsi itu.
Aturan yang akan saya bawa ke MCP server mana pun yang memegang credential: jadikan credential sebagai batas, pakai filter server sebagai dinding kedua, dan buktikan dindingnya dengan melihat apa yang benar-benar diterima client. read_only, toolsets dan denied_resources adalah setting yang bagus, tetapi satu typo bisa mematikannya tanpa suara, dan hanya audit tools/list yang menyadarinya.
Sumber dan bacaan lanjutan
- containers/kubernetes-mcp-server di GitHub: README, daftar tool dan fitur
- Dokumentasi Kubernetes MCP Server: referensi konfigurasi (read_only, toolsets, denied_resources, confirmation rules, Helm)
- Dokumentasi Kubernetes MCP Server: perubahan konfigurasi, flag CLI yang dihapus dan key tak dikenal
- Dokumentasi Kubernetes MCP Server: deploy dengan ServiceAccount read-only
- Dokumentasi Kubernetes MCP Server: tool RBAC metadata
- Release notes GitHub: kubernetes-mcp-server v0.0.67
- Dokumentasi Kubernetes: otorisasi RBAC dan ClusterRole view
- Spesifikasi Model Context Protocol: tools, anotasi dan pertimbangan keamanan

