Skip to main content
Glama
ziyuyu23

k8s-readonly-mcp

by ziyuyu23
README.md
# k8s-readonly-mcp

A **read-only** [Model Context Protocol](https://modelcontextprotocol.io) (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_namespaces` | List all namespaces |
| `list_pods` | List pods in a namespace (or all namespaces) |
| `describe_pod` | Full status, events, and config for one pod |
| `get_pod_logs` | Last N lines of a pod's logs |
| `list_deployments` | Deployments and their ready/up-to-date status |

## Quick start

> **Prerequisite:** `kubectl` must be installed and on your `PATH`.

```bash
# 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-mcp
```

### Connect it to Claude Desktop

Add this to your Claude Desktop MCP config
(`~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):

```json
{
  "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 `kubectl` execution 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](LICENSE).

TDQS

A3.7/5.0

Scored across 5 tools

Disambiguation5/5

Each tool targets a distinct Kubernetes resource or operation: describe_pod for pod details, get_pod_logs for logs, list_deployments for deployments, list_namespaces for namespaces, list_pods for pods. No overlap in functionality.

Naming Consistency5/5

All tool names follow a consistent verb_noun pattern (e.g., list_pods, describe_pod, get_pod_logs) using snake_case throughout, making them predictable and easy to understand.

Tool Count5/5

With 5 tools, the server is well-scoped for a read-only Kubernetes interface. It provides essential operations without unnecessary bloat, fitting the typical 3-15 tool range.

Completeness3/5

While core pod and deployment listing is covered, the server lacks operations for other common Kubernetes resources like services, nodes, events, or configmaps, which are expected in a read-only toolset.

Maintenance

ActivityStale
ResponsivenessNo issues