Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

49 Commits

Repository files navigation

Generic Kubernetes MCP Server

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:

Release pattern

This repo follows a tag-driven release pattern.

  • Pull requests merge into main
  • Merges to main that change internal/** or cmd/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 :latest tag in both registries
  • A manual promotion workflow can promote any released version to the floating stable channel 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.0

Stable promotion flow:

  • Run the promote-stable workflow manually
  • Provide an existing release tag such as v0.2.0
  • The workflow will:
    • move the mutable git tag stable to that release
    • promote ghcr.io/vk7416/generic-k8s-mcp:v0.2.0 to ghcr.io/vk7416/generic-k8s-mcp:stable
    • promote docker.io/bullraju/generic-k8s-mcp:v0.2.0 to docker.io/bullraju/generic-k8s-mcp:stable

Architecture

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
Loading

Request flow

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

What this gives you

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.

Status

This is an MVP scaffold. It implements a minimal JSON-RPC/MCP stdio server in Go and exposes read-only Kubernetes tools.

Design principles

  1. Use existing Kubernetes auth: local mode uses kubeconfig/context; in-cluster mode uses the Pod ServiceAccount.
  2. RBAC is the source of truth: every Kubernetes API call is checked with SelfSubjectAccessReview before running.
  3. Read-only by default: no create, update, patch, delete, exec, port-forward, scale, apply, or secret reads by default.
  4. Generic Kubernetes first: works with GKE, EKS, AKS, kubeadm, kind, minikube, and on-prem clusters.
  5. Cloud-specific integrations later: GKE/EKS/AKS plugins can be added later without changing the core.

Current tools

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.

Quick start: local mode

Use this first. Local mode is the simplest and safest because it uses the same kubeconfig/context access as kubectl.

1. Clone the repo

git clone https://github.com/vk7416/generic-k8s-mcp.git
cd generic-k8s-mcp

2. Install dependencies and build

go mod tidy
make build

This creates:

bin/k8s-mcp-server

3. Confirm your Kubernetes context

kubectl config current-context
kubectl get ns

Optional but recommended:

kubectl auth can-i list pods -A
kubectl auth can-i get pods/log -n default
kubectl auth can-i list nodes

The MCP server will only be able to do what this context can do.

4. Define local helper variables

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"

5. Run the MCP server manually

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=false

Use 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=false

Connect it to an MCP client

The 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.

Shared command 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 CLI

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=false

Verify 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 = true

Example 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

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"
      ]
    }
  }
}
EOF

Then 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

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=false

Verify:

claude mcp list
claude mcp get generic-k8s

Inside 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

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.

Manual JSON-RPC test

You can also test the server without an MCP client.

Start the server:

./bin/k8s-mcp-server --mode=local --namespace=default

Then 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=default

List 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=default

In-cluster mode

In-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.yaml

In-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.

Security defaults

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

Repository layout

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

Troubleshooting

failed to initialize Kubernetes clients

Check kubeconfig and context:

kubectl config current-context
kubectl get ns

Then run with an explicit kubeconfig:

./bin/k8s-mcp-server --mode=local --kubeconfig="$HOME/.kube/config"

Access denied from a tool

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 default

Metrics tool fails

The cluster may not have metrics-server or a compatible metrics.k8s.io API installed.

Check:

kubectl top pods -A
kubectl top nodes

MCP client cannot find the server

Use an absolute path for the server binary:

cd generic-k8s-mcp
pwd
ls -l bin/k8s-mcp-server

Then use:

/full/path/to/generic-k8s-mcp/bin/k8s-mcp-server

MCP client starts but tools fail

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

MVP limitations

  • 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.

License

Apache-2.0

About

鈽革笍 馃 Generic K8s MCP - Inspect clusters safely with the same trust model as 'kubectl'

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages