NAME

MCP::K8s - MCP Server for Kubernetes with RBAC-aware dynamic tools

VERSION

version 0.001

SYNOPSIS

# Start the MCP server on stdio (for Claude Desktop, Claude Code, etc.)
use MCP::K8s;
MCP::K8s->run_stdio;

# Or use the included script:
$ mcp-k8s

# Configure via environment variables:
$ export MCP_K8S_CONTEXT="my-cluster"
$ export MCP_K8S_NAMESPACES="default,production"
$ mcp-k8s

# Direct token authentication:
$ export MCP_K8S_TOKEN="eyJhbGci..."
$ export MCP_K8S_SERVER="https://my-cluster:6443"
$ mcp-k8s

# In-cluster: auto-detects when running as a Kubernetes pod

# Programmatic usage with custom API:
use MCP::K8s;
my $k8s = MCP::K8s->new(
  api        => $my_kubernetes_rest_instance,
  namespaces => ['default', 'staging'],
);
$k8s->server->to_stdio;

DESCRIPTION

MCP::K8s provides an MCP (Model Context Protocol) server that gives AI assistants like Claude access to Kubernetes clusters.

The key innovation: the server dynamically discovers what the connected service account can do via RBAC and only exposes those capabilities as MCP tools. A read-only service account gets read-only tools; a cluster-admin gets everything. Tool descriptions include the specific resources and namespaces available, so the LLM always knows exactly what it can do.

How it works

1. Connect — Authenticates via direct token, in-cluster service account, or kubeconfig
2. Discover — Submits SelfSubjectRulesReview requests to discover RBAC permissions per namespace
3. Register — Creates MCP tools with dynamic descriptions reflecting actual permissions
4. Serve — Runs the MCP protocol over stdio, checking permissions on every tool call

Why generic tools?

Kubernetes has 50+ built-in resource types plus unlimited Custom Resources. Instead of creating hundreds of specific tools (list_pods, get_deployment, delete_configmap...), MCP::K8s uses 10 generic tools with a resource parameter — the same pattern as kubectl get, kubectl delete, etc. This keeps the tool count manageable for MCP clients while supporting every resource type including CRDs.

Built on top of Kubernetes::REST (API client), IO::K8s (typed objects), and MCP::Server (protocol implementation).

context_name

Optional. Kubeconfig context name to use. Read from $ENV{MCP_K8S_CONTEXT} by default. If not set, the kubeconfig's current-context is used.

token

Optional. Bearer token for direct authentication. Read from $ENV{MCP_K8S_TOKEN} by default. When set, bypasses kubeconfig entirely and connects using this token directly.

server_endpoint

Optional. Kubernetes API server URL. Read from $ENV{MCP_K8S_SERVER} by default. Used with "token" for direct authentication, or with in-cluster auth. Defaults to https://kubernetes.default.svc.cluster.local when running in-cluster.

namespaces

ArrayRef of namespace names to operate on. Configured via:

  • $ENV{MCP_K8S_NAMESPACES} — comma-separated list (e.g. "default,production")

  • Auto-discovery — lists all namespaces from the cluster

  • Fallback — ['default'] if discovery fails

api

Kubernetes::REST instance for cluster communication. Built automatically from kubeconfig using Kubernetes::REST::Kubeconfig. Can be provided directly for testing or custom configurations.

permissions

MCP::K8s::Permissions instance holding the discovered RBAC permissions. Built and populated automatically on first access via SelfSubjectRulesReview.

json

JSON::MaybeXS encoder instance. Configured with utf8, pretty, canonical, and convert_blessed for consistent, readable output.

server

MCP::Server instance with all MCP tools registered. Built lazily, which triggers RBAC discovery and tool registration. See "MCP TOOLS" for the full list of registered tools.

_resource_plural

my $plural = $self->_resource_plural('Pod');       # => 'pods'
my $plural = $self->_resource_plural('Ingress');   # => 'ingresses'

Convert a Kubernetes Kind name (e.g. Pod, Deployment) to its plural form used in RBAC rules (e.g. pods, deployments). Uses a 4-tier lookup:

1. Static %RESOURCE_PLURALS map (fast, zero-cost)
2. IO::K8s class resource_plural() method (supports CRDs like Cilium)
3. API server discovery cache (lazy, one-time query)
4. Heuristic fallback (lowercase + simple pluralization)

_discover_resource_plurals

$self->_discover_resource_plurals;

Query the API server's discovery endpoints (/api/v1 and /apis) to build a Kind-to-plural mapping. Results are cached in "_resource_plurals_cache". Failures are silently ignored — the cache simply remains empty and callers fall through to heuristic pluralization.

_resolve_namespace

my $ns = $self->_resolve_namespace($args);

Resolve the namespace for a tool call. If $args->{namespace} is provided, uses that. Otherwise, if only one namespace is accessible, auto-fills it. Returns undef if the namespace cannot be determined (the tool should handle this case).

_format_resource_summary

my $summary = $self->_format_resource_summary($io_k8s_object);

Extract a concise summary hashref from an IO::K8s object, suitable for LLM consumption. Includes metadata (name, namespace, labels, creation time), kind, status fields (phase, replicas, conditions), and key spec fields (containers, ports, type).

The summary is intentionally compact — for full details, the k8s_get tool with output => 'json' should be used.

_format_list

my $summaries = $self->_format_list($list->items);

Format an arrayref of IO::K8s objects into an arrayref of summary hashrefs using "_format_resource_summary".

MCP TOOLS

All tools are registered on the "server" during construction. Each tool checks RBAC permissions before executing and returns clear error messages on denial. Tool descriptions dynamically include which resources and namespaces are available.

k8s_permissions

Show what the current Kubernetes service account is allowed to do. Returns a Markdown-formatted RBAC summary. The LLM should call this first to understand its capabilities.

No parameters required.

k8s_list

List Kubernetes resources with optional filtering.

Parameters:

resource (string, required) — Resource type: Pod, Deployment, Service, ConfigMap, etc.
namespace (string) — Target namespace. Auto-detected if only one is accessible.
label_selector (string) — Label filter, e.g. app=web,env=prod
field_selector (string) — Field filter, e.g. status.phase=Running

Returns JSON with count and items (array of resource summaries).

k8s_get

Get a single Kubernetes resource by name.

Parameters:

resource (string, required) — Resource type
name (string, required) — Resource name
namespace (string) — Target namespace
output (string) — Format: summary (default), json, or yaml

k8s_create

Create a Kubernetes resource from a manifest. The apiVersion and kind fields are auto-populated from the resource type via IO::K8s.

Parameters:

resource (string, required) — Resource type
manifest (object, required) — Resource manifest (metadata, spec, etc.)
namespace (string) — Target namespace (also auto-populated in metadata)

Returns JSON confirmation with the created resource name.

k8s_patch

Partially update a Kubernetes resource.

Parameters:

resource (string, required) — Resource type
name (string, required) — Resource name
patch (object, required) — Fields to change
namespace (string) — Target namespace
patch_type (string) — Strategy: strategic (default), merge, or json

See "patch" in Kubernetes::REST for details on patch strategies.

k8s_delete

Delete a Kubernetes resource by name.

Parameters:

resource (string, required) — Resource type
name (string, required) — Resource name
namespace (string) — Target namespace

k8s_logs

Get container logs from a pod. Essential for debugging. Uses the raw /api/v1/namespaces/{ns}/pods/{name}/log endpoint.

Parameters:

name (string, required) — Pod name
namespace (string) — Target namespace (required for logs)
container (string) — Container name (required for multi-container pods)
tail_lines (integer) — Number of lines from end (default: 100)
previous (boolean) — Get logs from previous container instance

k8s_events

Get Kubernetes events for debugging. Supports filtering by involved object name and arbitrary field selectors.

Parameters:

namespace (string) — Target namespace
involved_object (string) — Filter events by object name (e.g. pod name)
field_selector (string) — Field selector filter (e.g. reason=BackOff)

Returns JSON with count and items (array of event summaries).

k8s_rollout_restart

Trigger a rolling restart of a Deployment, StatefulSet, or DaemonSet. Works by patching the pod template annotation kubectl.kubernetes.io/restartedAt with the current timestamp — the same mechanism as kubectl rollout restart.

Parameters:

resource (string, required) — One of Deployment, StatefulSet, DaemonSet
name (string, required) — Resource name
namespace (string) — Target namespace

k8s_apply

Create or update a Kubernetes resource (like kubectl apply). Tries to create the resource first; if it already exists (409 Conflict), falls back to a strategic merge patch.

Parameters:

resource (string, required) — Resource type (e.g. Deployment, ConfigMap)
manifest (object, required) — Resource manifest. Must include metadata.name.
namespace (string) — Target namespace

Returns JSON confirmation with the action taken (created or updated).

run_stdio

# As class method:
MCP::K8s->run_stdio;

# As instance method:
my $k8s = MCP::K8s->new(%opts);
$k8s->run_stdio;

Start the MCP server on stdio. If called as a class method, creates a new instance first. This is the main entry point used by the mcp-k8s script.

ENVIRONMENT

KUBECONFIG

Path to kubeconfig file. Default: ~/.kube/config. Standard Kubernetes environment variable, also used by kubectl.

MCP_K8S_CONTEXT

Kubeconfig context to use. Default: the kubeconfig's current-context.

MCP_K8S_TOKEN

Bearer token for direct authentication. Bypasses kubeconfig entirely. Useful for CI/CD pipelines or when you have a service account token.

MCP_K8S_SERVER

Kubernetes API server URL. Used with MCP_K8S_TOKEN or in-cluster auth. Default when in-cluster: https://kubernetes.default.svc.cluster.local.

MCP_K8S_NAMESPACES

Comma-separated list of namespaces to operate on. Default: auto-discovered from the cluster (lists all namespaces the service account can see). Falls back to default if discovery fails.

AUTHENTICATION

MCP::K8s supports three authentication methods, tried in order:

1. Direct token — Set MCP_K8S_TOKEN (and optionally MCP_K8S_SERVER)
2. In-cluster — Auto-detected when running as a Kubernetes pod (reads mounted service account token from /var/run/secrets/kubernetes.io/serviceaccount/token)
3. Kubeconfig — Reads ~/.kube/config (or $KUBECONFIG), optionally filtered by MCP_K8S_CONTEXT

For in-cluster and direct token auth, the CA certificate at /var/run/secrets/kubernetes.io/serviceaccount/ca.crt is automatically used if present.

RBAC SETUP

Use a dedicated ServiceAccount with minimal permissions for AI access. Example RBAC manifests are included in the examples/ directory:

examples/readonly-serviceaccount.yaml — Read-only access (recommended starting point)
examples/deployer-serviceaccount.yaml — Read + deploy/restart capabilities
examples/full-ops-serviceaccount.yaml — Full access except secrets

RBAC is the single source of truth — if the service account shouldn't have access, don't grant it via RBAC. MCP::K8s does not implement application-layer permission filtering.

CLAUDE DESKTOP INTEGRATION

Add this to your Claude Desktop MCP configuration (~/.config/claude/claude_desktop_config.json):

{
  "mcpServers": {
    "kubernetes": {
      "command": "mcp-k8s",
      "env": {
        "MCP_K8S_CONTEXT": "my-cluster",
        "MCP_K8S_NAMESPACES": "default,production"
      }
    }
  }
}

CLAUDE CODE INTEGRATION

Add to your project's .mcp.json or global MCP settings:

{
  "mcpServers": {
    "kubernetes": {
      "command": "mcp-k8s",
      "env": {
        "MCP_K8S_CONTEXT": "dev-cluster"
      }
    }
  }
}

LANGERTHA RAIDER INTEGRATION

Use Langertha::Raider to build an autonomous AI agent that can interact with your Kubernetes cluster using MCP::K8s as its tool source:

use IO::Async::Loop;
use Future::AsyncAwait;
use Net::Async::MCP;
use Langertha::Engine::Anthropic;
use Langertha::Raider;
use MCP::K8s;

my $k8s = MCP::K8s->new(
  namespaces => ['default', 'production'],
);

my $loop = IO::Async::Loop->new;
my $mcp = Net::Async::MCP->new(server => $k8s->server);
$loop->add($mcp);

async sub main {
  await $mcp->initialize;

  my $engine = Langertha::Engine::Anthropic->new(
    api_key     => $ENV{ANTHROPIC_API_KEY},
    model       => 'claude-sonnet-4-6',
    mcp_servers => [$mcp],
  );

  my $raider = Langertha::Raider->new(
    engine  => $engine,
    mission => 'You are a Kubernetes operations assistant. '
             . 'Always check permissions first, then help the user '
             . 'investigate and manage their cluster.',
  );

  # First raid: discover capabilities
  my $r1 = await $raider->raid_f('What can I do on this cluster?');
  say $r1;

  # Second raid: uses history from the first
  my $r2 = await $raider->raid_f('List all pods and check for any issues.');
  say $r2;
}

main()->get;

The Raider maintains conversation history across raids, so the LLM can reference earlier context (e.g. the RBAC permissions it discovered) in follow-up interactions.

A ready-to-run demo is included in examples/raider-configmap-demo.pl — an AI creates, reads, updates, and deletes a ConfigMap. See the script's POD for requirements.

SECURITY CONSIDERATIONS

  • The server inherits the permissions of whatever kubeconfig context it connects with. Use a dedicated service account with minimal RBAC permissions for AI assistant access.

  • All tool calls check RBAC permissions before executing. Even if the service account has broad permissions, the permission check provides a clear audit trail.

  • Secrets are supported as a resource type. If your service account can read secrets, the LLM will be able to read them too. Consider excluding secrets from RBAC roles used for AI access.

SEE ALSO

MCP::K8s::Permissions — RBAC discovery engine

MCP::Kubernetes — Alias for this module

Langertha::Raider — Autonomous agent with conversation history and MCP tools

Kubernetes::REST — The underlying Kubernetes API client

IO::K8s — Typed Kubernetes resource objects

MCP::Server — MCP protocol implementation

Kubernetes::REST::Kubeconfig — Kubeconfig parsing

https://modelcontextprotocol.io/ — Model Context Protocol specification

https://kubernetes.io/docs/reference/access-authn-authz/rbac/ — Kubernetes RBAC

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-mcp-k8s/issues.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <torsten@raudssus.de>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.