k8s-readonly-mcp
k8s-readonly-mcp
A read-only Model Context Protocol (MCP) server that lets an LLM inspect a Kubernetes cluster — list pods, read logs, describe resources — but never mutate it.
Why I built it
I spend a lot of time embedded in customer Kubernetes environments. The most common thing I want from an LLM is "help me understand what's happening in this cluster" — without ever giving it the ability to change anything. So I built the safety in structurally rather than trusting the model to behave.
Every tool call routes through a single function that checks the kubectl verb against an
allow-list of read-only verbs (get, describe, logs, top, …). There is no code path that
can apply, delete, scale, or exec. If the model asks for a mutation, the server refuses.
This is the same least-privilege, human-in-the-loop instinct that any production agent needs.
What it does
Tool | Description |
| List all namespaces |
| List pods in a namespace (or all namespaces) |
| Full status, events, and config for one pod |
| Last N lines of a pod's logs |
| Deployments and their ready/up-to-date status |
Quick start
Prerequisite:
kubectlmust be installed and on yourPATH.
# 1. Install (using uv — https://docs.astral.sh/uv/)
uv sync
# 2. Make sure kubectl points at a cluster.
# A local cluster is perfect for trying this safely:
# kind create cluster (or: minikube start)
kubectl get nodes
# 3. Run the server
uv run k8s-readonly-mcpConnect it to Claude Desktop
Add this to your Claude Desktop MCP config
(~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"k8s-readonly": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/k8s-readonly-mcp", "run", "k8s-readonly-mcp"]
}
}
}Then ask Claude things like "What pods are failing in the default namespace, and why?" — it will
call list_pods and describe_pod, reason over the output, and explain — but it physically cannot
change your cluster.
Design decisions
Allow-list, not block-list. I enumerate what's permitted rather than trying to block bad verbs. A block-list is one forgotten verb away from a mutation; an allow-list fails closed.
One chokepoint. All
kubectlexecution goes through_run_kubectl. The security boundary is one function you can audit in 30 seconds.Timeouts and clear errors. Calls time out and surface
kubectl's stderr instead of hanging or failing silently — the model gets actionable feedback.
What I'd do next
Add resource-level scoping (restrict to specific namespaces per connection).
Stream large log outputs instead of buffering.
Add a small eval that checks the server refuses every mutating verb.
License
MIT — see LICENSE.