Kubernetes Read Only MCP Server

Created By
your-ko2 months ago
# mcp-k8s-ro A read-only MCP server that gives Claude access to Kubernetes clusters. Built in Go, communicates over stdio using the MCP protocol. ## Design - **Read-only** — only `get`, `describe`, `logs`, and `top` style operations. No create, update, or delete. If a mutating operation is needed, the server prints the equivalent `kubectl` command for you to run manually. Safe to use while on-call at night: Claude can never accidentally mutate your cluster, even under prompt fatigue. - **Secret-safe** — secret values are masked before being sent to the model, so your secrets cannot leak due to misconfiguration or prompt injection. - **Token-efficient** — responses include only relevant fields (name, status, restarts, etc.) rather than raw Kubernetes API objects, keeping context usage low. - **Cluster-aware** — every response includes the active context and cluster name, so Claude always knows which cluster it is talking to. - **Context-pinned** — the server locks to the active kubeconfig context at startup. Switching contexts in another terminal has no effect on the running server. - **No extra infra** — runs as a local binary or Docker container, connects to whatever kubeconfig context is active at startup. ## Redacted fields | Object/Field | Reason | |--------------------------------------------------------|----------------------------------------------------------| | Secret.data | Secret leak prevention | | Secret.stringData | Secret leak prevention | | CertificateSigningRequest.spec.request | Large base64 PEM blob, no diagnostic value, saves tokens | | Certificate (cert-manager) .spec.keystores | Cert chain PEM blobs, no diagnostic value, saves tokens | | Certificate (cert-manager) status.conditions[].message | Cert chain PEM blobs, no diagnostic value, saves tokens | | *.managedFields | No diagnostic value, saves tokens | ## Tools | Tool | Description | |---------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `k8s_list_resources` | List any resource type by name — pods, deployments, CRDs, etc. Accepts optional namespace filter. Returns name, status, readiness, restarts, node, IP, and more depending on resource kind. | | `k8s_describe_resource` | Return the full YAML of a single resource. Secret data is masked. | | `k8s_list_resource_types` | List all available resource types via the discovery API. Accepts optional API group filter. | | `k8s_get_logs` | Fetch pod logs. Supports container selector, tail lines, and `--previous` for crashed containers. | | `k8s_get_events` | List Kubernetes events for a namespace or the whole cluster, sorted by most recent. | | `k8s_top_pods` | CPU and memory usage per pod, with per-container breakdown. Requires metrics-server. | | `k8s_top_nodes` | CPU and memory usage per node, with percentage of allocatable capacity. Requires metrics-server. | ## Configuration | Environment variable | Default | Description | |----------------------|------------------|-------------------------| | `KUBECONFIG` | `~/.kube/config` | Path to kubeconfig file | ## Usage with Claude ### Binary Build the binary and add it to your Claude Desktop or `claude` CLI configuration: ```bash make build # binary is written to bin/mcp-k8s-ro ``` ```json { "mcpServers": { "k8s": { "type" : "stdio", "command": "/path/to/bin/mcp-k8s-ro", "env": { "KUBECONFIG": "/path/to/.kube/config" } } } } ``` Or via the CLI: ```bash claude mcp add --transport stdio --scope user mcp-k8s-ro [path to binary] ``` ### Docker Pull the image from GitHub Container Registry (pinning a specific version is recommended): ```bash docker pull ghcr.io/your-ko/mcp-k8s-ro:latest ``` Add it to your Claude Desktop or `claude` CLI configuration. The kubeconfig directory is mounted read-only into the container: ```json { "mcpServers": { "k8s": { "command": "docker", "args": [ "run", "--rm", "-i", "-v", "/path/to/.kube:/home/nonroot/.kube:ro", "ghcr.io/your-ko/mcp-k8s-ro:latest" ] } } } ``` If your kubeconfig is in a non-standard location, pass it via `KUBECONFIG`: ```json { "mcpServers": { "k8s": { "command": "docker", "args": [ "run", "--rm", "-i", "-e", "KUBECONFIG=/config/my-kubeconfig", "-v", "/path/to/my-kubeconfig:/config/my-kubeconfig:ro", "ghcr.io/your-ko/mcp-k8s-ro:latest" ] } } } ``` ## Single-cluster design The server intentionally operates on one kubeconfig context and provides no tool to switch clusters at runtime. The reasons are: - **Prompt injection isolation** — a malicious value in one cluster's resources (e.g. a pod annotation) cannot instruct Claude to pivot to a different cluster, including production. - **Explicit audit boundary** — every tool response includes the context and cluster name, so there is never ambiguity about which cluster was queried. **To point the server at a different cluster**, stop the server, switch context, and restart: ```bash kubectl config use-context my-other-cluster # then restart the MCP server / reload Claude Desktop ``` **To work with multiple clusters simultaneously**, register a separate server instance per cluster in your MCP config: ```json { "mcpServers": { "k8s-staging": { "type": "stdio", "command": "/path/to/bin/mcp-k8s-ro", "env": { "KUBECONFIG": "/path/to/.kube/config" } }, "k8s-prod": { "type": "stdio", "command": "/path/to/bin/mcp-k8s-ro", "env": { "KUBECONFIG": "/path/to/.kube/config-prod" } } } } ``` Claude will address each [.claude.json](../../.claude.json)server by name and each instance only ever sees its own cluster.
Overview

mcp-k8s-ro

A read-only MCP server that gives Claude access to Kubernetes clusters. Built in Go, communicates over stdio using the MCP protocol.

Design

  • Read-only — only get, describe, logs, and top style operations. No create, update, or delete. If a mutating operation is needed, the server prints the equivalent kubectl command for you to run manually. Safe to use while on-call at night: Claude can never accidentally mutate your cluster, even under prompt fatigue.
  • Secret-safe — secret values are masked before being sent to the model, so your secrets cannot leak due to misconfiguration or prompt injection.
  • Token-efficient — responses include only relevant fields (name, status, restarts, etc.) rather than raw Kubernetes API objects, keeping context usage low.
  • Cluster-aware — every response includes the active context and cluster name, so Claude always knows which cluster it is talking to.
  • Context-pinned — the server locks to the active kubeconfig context at startup. Switching contexts in another terminal has no effect on the running server.
  • No extra infra — runs as a local binary or Docker container, connects to whatever kubeconfig context is active at startup.

Redacted fields

Object/FieldReason
Secret.dataSecret leak prevention
Secret.stringDataSecret leak prevention
CertificateSigningRequest.spec.requestLarge base64 PEM blob, no diagnostic value, saves tokens
Certificate (cert-manager) .spec.keystoresCert chain PEM blobs, no diagnostic value, saves tokens
Certificate (cert-manager) status.conditions[].messageCert chain PEM blobs, no diagnostic value, saves tokens
*.managedFieldsNo diagnostic value, saves tokens

Tools

ToolDescription
k8s_list_resourcesList any resource type by name — pods, deployments, CRDs, etc. Accepts optional namespace filter. Returns name, status, readiness, restarts, node, IP, and more depending on resource kind.
k8s_describe_resourceReturn the full YAML of a single resource. Secret data is masked.
k8s_list_resource_typesList all available resource types via the discovery API. Accepts optional API group filter.
k8s_get_logsFetch pod logs. Supports container selector, tail lines, and --previous for crashed containers.
k8s_get_eventsList Kubernetes events for a namespace or the whole cluster, sorted by most recent.
k8s_top_podsCPU and memory usage per pod, with per-container breakdown. Requires metrics-server.
k8s_top_nodesCPU and memory usage per node, with percentage of allocatable capacity. Requires metrics-server.

Configuration

Environment variableDefaultDescription
KUBECONFIG~/.kube/configPath to kubeconfig file

Usage with Claude

Binary

Build the binary and add it to your Claude Desktop or claude CLI configuration:

make build
# binary is written to bin/mcp-k8s-ro
{
  "mcpServers": {
    "k8s": {
      "type" : "stdio",
      "command": "/path/to/bin/mcp-k8s-ro",
      "env": {
        "KUBECONFIG": "/path/to/.kube/config"
      }
    }
  }
}

Or via the CLI:

claude mcp add --transport stdio --scope user mcp-k8s-ro [path to binary]

Docker

Pull the image from GitHub Container Registry (pinning a specific version is recommended):

docker pull ghcr.io/your-ko/mcp-k8s-ro:latest

Add it to your Claude Desktop or claude CLI configuration. The kubeconfig directory is mounted read-only into the container:

{
  "mcpServers": {
    "k8s": {
      "command": "docker",
      "args": [
        "run", "--rm", "-i",
        "-v", "/path/to/.kube:/home/nonroot/.kube:ro",
        "ghcr.io/your-ko/mcp-k8s-ro:latest"
      ]
    }
  }
}

If your kubeconfig is in a non-standard location, pass it via KUBECONFIG:

{
  "mcpServers": {
    "k8s": {
      "command": "docker",
      "args": [
        "run", "--rm", "-i",
        "-e", "KUBECONFIG=/config/my-kubeconfig",
        "-v", "/path/to/my-kubeconfig:/config/my-kubeconfig:ro",
        "ghcr.io/your-ko/mcp-k8s-ro:latest"
      ]
    }
  }
}

Single-cluster design

The server intentionally operates on one kubeconfig context and provides no tool to switch clusters at runtime. The reasons are:

  • Prompt injection isolation — a malicious value in one cluster's resources (e.g. a pod annotation) cannot instruct Claude to pivot to a different cluster, including production.
  • Explicit audit boundary — every tool response includes the context and cluster name, so there is never ambiguity about which cluster was queried.

To point the server at a different cluster, stop the server, switch context, and restart:

kubectl config use-context my-other-cluster
# then restart the MCP server / reload Claude Desktop

To work with multiple clusters simultaneously, register a separate server instance per cluster in your MCP config:

{
  "mcpServers": {
    "k8s-staging": {
      "type": "stdio",
      "command": "/path/to/bin/mcp-k8s-ro",
      "env": { "KUBECONFIG": "/path/to/.kube/config" }
    },
    "k8s-prod": {
      "type": "stdio",
      "command": "/path/to/bin/mcp-k8s-ro",
      "env": { "KUBECONFIG": "/path/to/.kube/config-prod" }
    }
  }
}

Claude will address each .claude.jsonserver by name and each instance only ever sees its own cluster.

Server Config

{
  "mcpServers": {
    "k8s": {
      "command": "docker",
      "args": [
        "run",
        "--rm",
        "-i",
        "-v",
        "${HOME}/.kube:/root/.kube:ro",
        "ghcr.io/your-ko/mcp-k8s-ro:latest"
      ]
    }
  }
}
Project Info
Created At
2 months ago
Updated At
2 months ago
Author Name
your-ko
Star
-
Language
-
License
-
Category

Recommend Servers

View All
Bring your real authenticated browser session to AI coding agents. Local-first MCP server + Chrome MV3 extension. No cloud. No telemetry.
@Cubenest

peek records the user's actual logged-in browser (DOM via rrweb, console events, network metadata, optional response bodies via opt-in Deep capture) through a Chrome MV3 extension. The extension ships events through a native-messaging stdio bridge to a local MCP server (peek-mcp), which persists them to a SQLite database at ~/.peek/sessions.db. AI coding agents (Claude Code, Cursor, Cline, Windsurf) read sessions from the database via 10 MCP tools: Tool What it does list_recent_sessions List recently recorded sessions (id, origin, ts, event count). get_session_summary LLM-readable narrative summary of a session. get_session_console_errors Console errors recorded in a session. get_session_network_errors Failed/notable network requests in a session. get_user_action_before_error Last N user actions before a console error. generate_playwright_repro Generate a runnable Playwright test from a session. get_dom_snapshot Reconstruct the DOM at a given timestamp. query_dom_history Timeline of attribute/text changes for a selector. request_authorization Side-panel consent for write actions (Level 3). execute_action Dispatch a UI action (gated by permission level + destructive blocklist). Why local-first matters Every other "browser session for AI" tool ships to a vendor cloud. peek's SQLite + extension live on the user's machine — no remote endpoints, no telemetry. The privacy policy (docs/peek/PRIVACY_POLICY.md) is the source of truth. Install # 1. Add the MCP server to Claude Code claude mcp add peek -- npx -y @peekdev/mcp # 2. Install the Chrome extension from the Chrome Web Store # (link added once the CWS listing is approved)

a day ago