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
SelfSubjectRulesReviewrequests 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_PLURALSmap (fast, zero-cost) - 2.
IO::K8sclassresource_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=prodfield_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 typename(string, required) — Resource namenamespace(string) — Target namespaceoutput(string) — Format:summary(default),json, oryaml
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 typemanifest(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 typename(string, required) — Resource namepatch(object, required) — Fields to changenamespace(string) — Target namespacepatch_type(string) — Strategy:strategic(default),merge, orjson
See "patch" in Kubernetes::REST for details on patch strategies.
k8s_delete
Delete a Kubernetes resource by name.
Parameters:
resource(string, required) — Resource typename(string, required) — Resource namenamespace(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 namenamespace(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 namespaceinvolved_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 ofDeployment,StatefulSet,DaemonSetname(string, required) — Resource namenamespace(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 includemetadata.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 bykubectl. 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_TOKENor 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
defaultif discovery fails.
AUTHENTICATION
MCP::K8s supports three authentication methods, tried in order:
- 1. Direct token — Set
MCP_K8S_TOKEN(and optionallyMCP_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 byMCP_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 capabilitiesexamples/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
secretsfrom 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.