A read-only, context-aware Model Context Protocol (MCP) server for Kubernetes.
The goal is simple: let an AI assistant inspect Kubernetes clusters using the same access model as kubectl and K9s.
Project site and docs:
- GitHub Pages site source:
docs/ - Gateway hosting plan:
docs/gateway-plan.md
This repo follows a tag-driven release pattern.
- Pull requests merge into
main - Merges to
mainthat changeinternal/**orcmd/k8s-mcp-server/**automatically create the next patch tag - Releases are created from semantic version tags like
v0.2.0 - Each release publishes multi-arch container images to:
ghcr.io/vk7416/generic-k8s-mcp:<tag>docker.io/bullraju/generic-k8s-mcp:<tag>
- The release workflow also updates the floating
:latesttag in both registries - A manual promotion workflow can promote any released version to the floating
stablechannel in both registries - A GitHub Release is created for the same tag
Required GitHub secret for Docker Hub publishing:
DOCKERHUB_TOKEN
Example release flow:
git checkout main
git pull
git tag v0.2.0
git push origin v0.2.0Stable promotion flow:
- Run the
promote-stableworkflow manually - Provide an existing release tag such as
v0.2.0 - The workflow will:
- move the mutable git tag
stableto that release - promote
ghcr.io/vk7416/generic-k8s-mcp:v0.2.0toghcr.io/vk7416/generic-k8s-mcp:stable - promote
docker.io/bullraju/generic-k8s-mcp:v0.2.0todocker.io/bullraju/generic-k8s-mcp:stable
- move the mutable git tag
flowchart TD
U[User asks a Kubernetes question] --> A[AI client / MCP host]
A -->|MCP tools/list and tools/call| M[generic-k8s-mcp server]
subgraph Local_Mode[Local mode]
M --> KCFG[~/.kube/config]
KCFG --> CTX[Selected kube context]
end
subgraph In_Cluster_Mode[In-cluster mode]
M --> SA[Kubernetes ServiceAccount]
end
CTX --> API[Kubernetes API server]
SA --> API
M --> P[MCP read-only policy]
P --> RBAC[SelfSubjectAccessReview / Kubernetes RBAC]
RBAC --> API
API --> RES[Pods / Nodes / Deployments / Events / Logs / Metrics / CRDs]
RES --> M
M -->|Structured JSON + summary| A
A --> U
Natural language
-> AI client
-> MCP tool call
-> generic-k8s-mcp
-> read-only policy check
-> Kubernetes RBAC check
-> Kubernetes API read
-> structured result back to AI
You can ask natural-language questions such as:
Show unhealthy pods in namespace payments.
Why is deployment checkout-api not ready?
List nodes with pressure conditions.
Show warning events in kube-system.
Get the last 100 logs from pod api-123 in prod.
Can my current context list pods across all namespaces?
The server does not create its own admin access. It uses your existing kubeconfig context in local mode, or a Kubernetes ServiceAccount in in-cluster mode.
This is an MVP scaffold. It implements a minimal JSON-RPC/MCP stdio server in Go and exposes read-only Kubernetes tools.
- Use existing Kubernetes auth: local mode uses kubeconfig/context; in-cluster mode uses the Pod ServiceAccount.
- RBAC is the source of truth: every Kubernetes API call is checked with
SelfSubjectAccessReviewbefore running. - Read-only by default: no create, update, patch, delete, exec, port-forward, scale, apply, or secret reads by default.
- Generic Kubernetes first: works with GKE, EKS, AKS, kubeadm, kind, minikube, and on-prem clusters.
- Cloud-specific integrations later: GKE/EKS/AKS plugins can be added later without changing the core.
| Tool | Purpose |
|---|---|
cluster_info |
Show current access mode, context, namespace, and Kubernetes server version. |
can_i |
Check whether the current identity can perform a Kubernetes action. |
list_namespaces |
List visible namespaces. |
list_nodes |
List nodes, readiness, taints, capacity, and allocatable resources. |
describe_node |
Inspect a node's labels, taints, conditions, and resource info. |
list_pods |
List pods by namespace, label selector, and field selector. |
describe_pod |
Inspect pod phase, readiness, conditions, containers, warning events, and owning workload. |
get_pod_logs |
Read pod logs with optional container, tail, and since options. |
list_events |
List events in a namespace. |
list_deployments |
List deployment readiness and rollout status. |
describe_deployment |
Inspect one deployment. |
get_resource_usage |
Read pod or node usage from metrics.k8s.io when metrics-server is installed. |
find_unhealthy_workloads |
Summarize unhealthy pods/deployments and warning events. |
explain_resource |
Dynamically read any Kubernetes resource by apiVersion/kind/name. |
Use this first. Local mode is the simplest and safest because it uses the same kubeconfig/context access as kubectl.
git clone https://github.com/vk7416/generic-k8s-mcp.git
cd generic-k8s-mcpgo mod tidy
make buildThis creates:
bin/k8s-mcp-server
kubectl config current-context
kubectl get nsOptional but recommended:
kubectl auth can-i list pods -A
kubectl auth can-i get pods/log -n default
kubectl auth can-i list nodesThe MCP server will only be able to do what this context can do.
Run these from the repo root after make build:
MCP_BIN="$(pwd)/bin/k8s-mcp-server"
KUBE_CONFIG="$HOME/.kube/config"
KUBE_CONTEXT="$(kubectl config current-context)"
KUBE_NAMESPACE="default"Check them:
echo "$MCP_BIN"
echo "$KUBE_CONTEXT"Use your current kubeconfig context:
"$MCP_BIN" \
--mode=local \
--kubeconfig="$KUBE_CONFIG" \
--context="$KUBE_CONTEXT" \
--namespace="$KUBE_NAMESPACE" \
--readonly=true \
--allow-secret-read=false \
--allow-pod-command=falseUse a specific context:
"$MCP_BIN" \
--mode=local \
--kubeconfig="$HOME/.kube/config" \
--context=my-cluster-context \
--namespace=kube-system \
--readonly=true \
--allow-secret-read=false \
--allow-pod-command=falseThe server currently uses stdio transport. Most MCP clients start the server process for you, so you usually do not manually keep k8s-mcp-server running. Configure the client with the binary path and args.
All clients below use the same server command:
MCP_BIN="$(pwd)/bin/k8s-mcp-server"
KUBE_CONFIG="$HOME/.kube/config"
KUBE_CONTEXT="$(kubectl config current-context)"
KUBE_NAMESPACE="default"Server args:
--mode=local
--kubeconfig=$KUBE_CONFIG
--context=$KUBE_CONTEXT
--namespace=$KUBE_NAMESPACE
--readonly=true
--allow-secret-read=false
--allow-pod-command=false
Codex stores MCP configuration in ~/.codex/config.toml, and the Codex CLI and IDE extension share this configuration.
From the repo root:
MCP_BIN="$(pwd)/bin/k8s-mcp-server"
KUBE_CONTEXT="$(kubectl config current-context)"
codex mcp add generic-k8s -- "$MCP_BIN" \
--mode=local \
--kubeconfig="$HOME/.kube/config" \
--context="$KUBE_CONTEXT" \
--namespace=default \
--readonly=true \
--allow-secret-read=false \
--allow-pod-command=falseVerify inside Codex:
/mcp
You can also edit ~/.codex/config.toml directly:
[mcp_servers.generic-k8s]
command = "/absolute/path/to/generic-k8s-mcp/bin/k8s-mcp-server"
args = [
"--mode=local",
"--kubeconfig=/Users/YOU/.kube/config",
"--context=YOUR_CONTEXT",
"--namespace=default",
"--readonly=true",
"--allow-secret-read=false",
"--allow-pod-command=false"
]
startup_timeout_sec = 20
tool_timeout_sec = 60
enabled = trueExample Codex prompts:
Use generic-k8s and run cluster_info.
Use generic-k8s and show unhealthy pods in namespace default.
Use generic-k8s and list warning events in kube-system.
Use generic-k8s and tell me whether my current context can list pods across all namespaces.
Cursor can use the standard MCP JSON config shape. For a project-local setup, create .cursor/mcp.json in the repo or workspace where you want Cursor to use the server.
From the repo root:
MCP_BIN="$(pwd)/bin/k8s-mcp-server"
KUBE_CONTEXT="$(kubectl config current-context)"
mkdir -p .cursor
cat > .cursor/mcp.json <<EOF
{
"mcpServers": {
"generic-k8s": {
"command": "$MCP_BIN",
"args": [
"--mode=local",
"--kubeconfig=$HOME/.kube/config",
"--context=$KUBE_CONTEXT",
"--namespace=default",
"--readonly=true",
"--allow-secret-read=false",
"--allow-pod-command=false"
]
}
}
}
EOFThen restart Cursor or reload the window. In Cursor chat/agent mode, ask:
Use the generic-k8s MCP server and run cluster_info.
Use generic-k8s to show unhealthy pods in namespace default.
Use generic-k8s to describe deployment api in namespace default.
For a global Cursor setup, use the same JSON shape in your global Cursor MCP configuration file if your Cursor version exposes one through Settings > MCP.
Claude Code supports local stdio MCP servers. Use -- to separate Claude's flags from the server command and server args.
From the repo root:
MCP_BIN="$(pwd)/bin/k8s-mcp-server"
KUBE_CONTEXT="$(kubectl config current-context)"
claude mcp add --transport stdio generic-k8s -- "$MCP_BIN" \
--mode=local \
--kubeconfig="$HOME/.kube/config" \
--context="$KUBE_CONTEXT" \
--namespace=default \
--readonly=true \
--allow-secret-read=false \
--allow-pod-command=falseVerify:
claude mcp list
claude mcp get generic-k8sInside Claude Code, check server status with:
/mcp
Example Claude prompts:
Use generic-k8s and run cluster_info.
Use generic-k8s and show warning events in kube-system.
Use generic-k8s and explain why pods are unhealthy in namespace default.
Claude Desktop uses the same mcpServers JSON shape. On macOS, edit:
~/Library/Application Support/Claude/claude_desktop_config.json
Example:
{
"mcpServers": {
"generic-k8s": {
"command": "/absolute/path/to/generic-k8s-mcp/bin/k8s-mcp-server",
"args": [
"--mode=local",
"--kubeconfig=/Users/YOU/.kube/config",
"--context=YOUR_CONTEXT",
"--namespace=default",
"--readonly=true",
"--allow-secret-read=false",
"--allow-pod-command=false"
]
}
}
}Restart Claude Desktop after editing the file.
You can also test the server without an MCP client.
Start the server:
./bin/k8s-mcp-server --mode=local --namespace=defaultThen send JSON-RPC messages from another shell using a simple pipe:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"manual","version":"dev"}}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"cluster_info","arguments":{}}}' \
| ./bin/k8s-mcp-server --mode=local --namespace=defaultList pods:
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"manual","version":"dev"}}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"list_pods","arguments":{"namespace":"default"}}}' \
| ./bin/k8s-mcp-server --mode=local --namespace=defaultIn-cluster mode is useful later when you want the MCP server to run inside Kubernetes using a dedicated ServiceAccount.
Apply manifests:
kubectl apply -f deploy/namespace.yaml
kubectl apply -f deploy/rbac-readonly.yaml
kubectl apply -f deploy/deployment.yamlIn-cluster mode uses:
system:serviceaccount:k8s-mcp:k8s-mcp-reader
Important: v1 is mainly a stdio MCP server. A production in-cluster deployment should add Streamable HTTP/SSE transport and strong auth before exposing it to users.
The server blocks risky operations at the MCP policy layer and then asks Kubernetes RBAC before every read. This gives two layers of control:
MCP readonly policy
+
Kubernetes RBAC
By default, the server does not expose tools for:
- reading Secrets
- exec into Pods
- port-forward
- applying YAML
- patching resources
- deleting resources
- scaling or restarting workloads
cmd/k8s-mcp-server/ CLI entrypoint
internal/mcp/ Minimal MCP JSON-RPC server
internal/kube/ Kubernetes client/context loading
internal/authz/ SelfSubjectAccessReview checks
internal/policy/ Read-only guardrails
internal/tools/ Kubernetes MCP tools
deploy/ Kubernetes deployment manifests
examples/ MCP client examples
docs/ Architecture and security notes
Check kubeconfig and context:
kubectl config current-context
kubectl get nsThen run with an explicit kubeconfig:
./bin/k8s-mcp-server --mode=local --kubeconfig="$HOME/.kube/config"Check RBAC:
kubectl auth can-i list pods -A
kubectl auth can-i get pods/log -n default
kubectl auth can-i list deployments.apps -n defaultThe cluster may not have metrics-server or a compatible metrics.k8s.io API installed.
Check:
kubectl top pods -A
kubectl top nodesUse an absolute path for the server binary:
cd generic-k8s-mcp
pwd
ls -l bin/k8s-mcp-serverThen use:
/full/path/to/generic-k8s-mcp/bin/k8s-mcp-server
Check your Kubernetes context and RBAC:
kubectl config current-context
kubectl auth can-i list pods -A
kubectl auth can-i get pods/log -n default- The stdio transport is implemented directly with newline-delimited JSON-RPC.
- HTTP/SSE or Streamable HTTP transport can be added later.
- No write operations are implemented.
- Cloud provider integrations are intentionally out of scope for v1.
Apache-2.0