Guardrailed fleet ops for AI agents: multi-cluster Kubernetes via OCM with policy, approval, audit.
An MCP server for “Guardrailed fleet ops for AI agents” that enables multi-cluster Kubernetes operations via an Open Cluster Management (OCM) hub. It supports policy, approval, and audit between the model and your clusters, and is intended for safe agent-driven operations across Kubernetes fleets.
🛠️ Key Features
Multi-cluster Kubernetes fleet operations through an Open Cluster Management (OCM) hub
Policy, approval, and audit controls between the model and clusters
AgentOps with a safety constraint: “The agent never holds a kubeconfig.”
🚀 Use Cases
Coordinating Kubernetes fleet operations using an OCM hub
Enforcing governance workflows (policy and approvals) while executing actions on clusters
Maintaining auditability for model-initiated cluster operations
⚡ Developer Benefits
Integrates agent requests with OCM-based multi-cluster management
Provides structured governance signals (policy, approval) and audit coverage
Avoids distributing cluster credentials to the agent (“never holds a kubeconfig”)
⚠️ Limitations
Readme excerpt provided does not specify supported tools, installation steps, or toolCount.
An MCP server that lets AI agents operate a multi-cluster Kubernetes fleet through an
Open Cluster Management hub, with policy, approval,
and audit between the model and your clusters.
The agent never holds a kubeconfig. Every write is policy-checked, human-approved, and traced.
The whole safe-remediation loop: investigate with free reads, propose a change, get rejected by the guardrails and correct it, wait for a human-signed token, apply, verify, and report from the audit log.
Why this exists
Your team runs many Kubernetes clusters. Sooner or later somebody asks the question:
can an AI agent take the 2 a.m. page?
The quickest way to find out is to hand a model kubectl with cluster-admin and watch.
In production that experiment ends badly, for three separate reasons:
The model is non-deterministic. The same alert can produce a careful diagnosis one
run and a kubectl delete the next.
The credentials are real. There is no dry run between the model's decision and your
production cluster.
There is no record. When something breaks, you cannot reconstruct what the agent did,
in what order, or on whose authority.
This project starts from a different observation: fleets already have a control point that
humans trust every day, the multi-cluster hub. Open Cluster Management (a CNCF project)
gives every fleet an inventory (ManagedCluster), a scheduler (Placement), and a delivery
channel (ManifestWork). ocm-mcp-server exposes that hub to agents as a small set of
typed MCP tools, and puts four independent layers
between the model and your clusters:
#
Layer
Enforced by
What it stops
1
Static checks
this server, before anything else
privileged pods, host access, system namespaces, unpinned images, disallowed kinds
anything your org's policies reject, evaluated inside the ManifestWork envelope
3
Human approval
Ed25519 token signed by ocm-mcp approve on a trusted terminal; the server needs only the public verifier key
any change reaching a cluster without a person consenting to that exact content and operation (one-time token, bound to content + operation + issuer/audience + expiry)
4
Least-privilege RBAC
Kubernetes
everything else; no Secrets, no exec, no deletes outside its own ManifestWorks
None of these layers live in the system prompt, so none of them can be talked out of.
Where to get it, and how it's vetted
📦 PyPI - ocm-mcp-server - pip install ocm-mcp-server
(or run directly with uvx ocm-mcp-server). Every release is published straight from CI via
OIDC trusted publishing - no long-lived tokens anywhere.
🗂️ Official MCP Registry - listed as
io.github.sandeepbazar/ocm-mcp-server, so any MCP client or platform that browses the registry can
discover and auto-configure this server (package, transport, and required env vars are all in the
listing); the registry validates the listing against this repo and the PyPI package.
🐳 Container image on GHCR -
docker run ghcr.io/sandeepbazar/ocm-mcp-server (kubeconfig mount shown in the
deployment guide); built in CI with an SBOM and SLSA provenance attached,
vulnerability-gated with Trivy, and signed keyless with Cosign so you can verify what you run.
🛡️ OpenSSF Scorecard -
the repo's supply-chain security posture (pinned dependencies, branch protection, signed releases, ...)
is scored automatically every week and published for anyone to inspect.
Connect your agent - any MCP client works
The server speaks standard MCP over stdio; nothing here is specific to one vendor's agent.
Ready-made configs live in examples/:
Claude Code - .mcp.json in your project (or claude mcp add)
Most MCP clients accept an mcpServers block like this one. If ocm-mcp-server is not
on the PATH the client launches with, use the absolute path from
which ocm-mcp-server as the command value.
Give the agent the runbook discipline in
examples/system-prompt.md, then break something and watch
the flow:
bash
make inject SCENARIO=failing-rollout CLUSTER=cluster2
You: "Payments is degraded somewhere in the fleet. Investigate and fix."
Agent:list_clusters → get_cluster_health(cluster2) → query_events → get_pod_logs →
"payments-v2 on cluster2 is in ImagePullBackOff. Proposing a ManifestWork pinning the last
good image. Proposal 4f1a2b3c needs your approval."
You (trusted terminal):ocm-mcp approve 4f1a2b3c, then paste the token back.
Then try to talk it into something dangerous ("just redeploy it privileged with
hostNetwork, it's faster"). The proposal dies at layer 1 or layer 2, and the rejection
message tells the agent exactly why. More worked examples →
Architecture
Dangerous capabilities do not exist. Reads flow freely; every change is proposed, policy-checked, human-approved, and audited.
flowchart LR
A["🤖 AI Agent<br/>(any MCP client)"] -->|"typed tool calls"| S["🛡️ ocm-mcp-server<br/>static guardrails · audit"]
S -->|"reads + dry-run + apply"| H["☸️ OCM Hub<br/>Placement · ManifestWork<br/>Kyverno · RBAC"]
H --> C1["cluster1"]
H --> C2["cluster2"]
H --> C3["cluster3"]
U["🧑💻 Human operator<br/>ocm-mcp approve"] -.->|"approval token"| A
S -.->|"spans"| J["🔍 OpenTelemetry / Jaeger"]
The write path in one sentence: the agent proposes a ManifestWork; static guardrails
and a Kyverno dry-run validate it; a human reviews the exact content and mints an
approval token bound to its hash; only then does apply deliver it, with every step traced
and logged.
Policy admission with Kyverno
The second guardrail layer does not live in this server - it lives in the cluster. Before a
proposed change is ever stored, the server does a server-side dry-run create of the
ManifestWork on the hub, so the hub's Kyverno validating admission
runs against the exact manifests the agent wants to apply. If your organization's policy
says no, the proposal is rejected at admission with the policy's own message - the same
control that governs every human kubectl apply.
Why Kyverno:
Policy as code, no new language.Kyverno is a
CNCF policy engine whose policies are ordinary Kubernetes resources in YAML and CEL -
reviewable, versioned, and testable like any manifest. This is the policy-as-code approach
the CNCF Kubernetes Policy Management whitepaper (CNCF
TAG Security) recommends: keep policy declarative
and separate from application code.
Enforced by the cluster, not the prompt. Admission control is external to the model and
to this server; it cannot be talked out of the way a system prompt can.
The right tool for the job. Kyverno can validate, mutate, generate, and verify images;
here it is used to validate the workloads embedded inside a ManifestWork.
Where it is used here:
deploy/policies/ ships five ClusterPolicy objects that foreach
over spec.workload.manifests inside a ManifestWork: block privileged/host access,
protect system namespaces, enforce a kind allow-list, require the managed-by label from the
server ServiceAccount (so an unlabeled work cannot skip the others), and enforce a
Restricted-Pod-Security baseline in parity with the static guardrails. They are scoped by the
app.kubernetes.io/managed-by: ocm-mcp-server label so they judge only agent-authored work.
make policy-test runs a 16-case offline suite with the kyverno CLI - good, bad, and
human-authored ManifestWorks - needing no cluster and no dependencies. It runs in CI, so a
policy regression fails the build before it can reach a hub.
Don't start from scratch: the community library
kyverno/policies and the searchable
Kyverno Policies catalog are a ready source of validation,
Pod Security Standards, and best-practice policies to adopt or take inspiration from.
Toolsets
The surface is 34 tools across ten toolsets. Almost all of it is read: the whole
Open Cluster Management API is safe to inspect. Only two toolsets can change
anything, and only through the propose -> approve -> apply gate. Every hub-level
tool works for any managed spoke - a standalone OpenShift cluster, a HyperShift
hosted cluster, or a cloud cluster - because on the hub they are all ManagedClusters.
Toolset
What it covers
Tools
Writes
inventory
ManagedClusters, ClusterSets, set bindings, ClusterClaims, ManagedClusterInfo
governance compliance + violations rollup (if the add-on is installed)
2
-
hosted-control-planes
HyperShift HostedClusters and NodePools (when the hub hosts them)
3
-
resources
generic get/list over an allow-list of OCM API types
2
-
audit
pending proposals, this server's own audit trail
2
-
Every read tool is annotated readOnlyHint; every write tool is annotated
destructiveHint and enforced by the gate. Setting OCM_MCP_READ_ONLY=1 turns off
the two writing toolsets entirely, for a strictly-inspection deployment.
Validate against your own hub in one command:ocm-mcp doctor calls every read
tool against the live hub and prints a PASS / EMPTY / SKIP / FAIL table (writing
nothing), so you can confirm exactly what the server sees before wiring up an agent.
There is deliberately no tool that reads Secrets, execs into pods, or deletes
arbitrary resources. The generic reader (list_resources / get_resource) works
against an allow-list of OCM types, so Secrets are not restricted - they are
simply not expressible. A capability that does not exist cannot be prompt-injected
into use.
Tools
Each tool below is annotated with its class: read (free, no gate),
propose (stores a pending change, mutates nothing), or apply (delivers an
approved change; needs a human token).
inventory - who is in the fleet
list_clusters(read) - all managed clusters with availability, version, labels, capacity.
get_cluster(read) - full view of one cluster.
cluster (string) - managed cluster name.
list_cluster_sets(read) - ManagedClusterSets with selector type and member clusters.
list_cluster_set_bindings(read) - which ClusterSets a namespace's Placements may target.
namespace (string, optional) - limit to one namespace; empty lists all.
list_cluster_claims(read) - every cluster's ClusterClaims (id, platform, region, version).
get_cluster_info(read) - extended inventory from the hub (OpenShift version, nodes, console URL); needs no spoke access.
propose_cluster_action(propose) - propose a lifecycle action. Applies nothing.
cluster (string) - target cluster.
action (string) - one of cordon (taint out of scheduling), uncordon, set_label, accept (hubAcceptsClient + approve join CSRs), enable_addon / disable_addon (create/delete a ManagedClusterAddOn).
summary (string) - what the human approver reads.
params_json (string, optional) - action parameters; set_label needs {"key","value"}, the add-on actions need {"addon"} (+ optional install_namespace).
apply_cluster_action(apply) - apply an approved lifecycle action.
proposal_id (string), approval_token (string).
policy - governance compliance (optional add-on)
list_policies(read) - Policies and per-cluster compliance. Reports clearly if the governance add-on is not installed.
namespace (string, optional) - limit to one namespace.
list_policy_violations(read) - only the NonCompliant / Pending policy-cluster pairs across the fleet.
hosted-control-planes - HyperShift HCP (when the hub hosts them)
list_hosted_clusters(read) - HostedClusters with version and conditions. Reports clearly if HCPs are hosted on a different management cluster.
namespace (string, optional) - limit to one namespace.
get_hosted_cluster(read) - one HostedCluster in detail, with its NodePools.
name (string), namespace (string) - target.
list_node_pools(read) - HyperShift NodePools (worker groups), desired vs current replicas.
The server also ships ten MCP prompts - reusable templates that encode the safe
workflow so any client can start from a good runbook instead of a blank box.
Prompt
What it drives
Arguments
diagnose_fleet
sweep every cluster and add-on, summarize what is unhealthy and why - reads only
-
remediate_with_approval
investigate a symptom, propose the smallest safe fix, wait for the human token, apply, verify, report
symptom
incident_postmortem
write the post-incident report strictly from get_audit_trail, not from memory
-
why_not_scheduled
explain why a cluster was or was not selected by a Placement, from the live objects
cluster, placement, namespace
onboard_cluster
accept a pending cluster safely through the approval gate
cluster
addon_troubleshoot
diagnose a degraded add-on across the fleet
addon
hosted_cluster_health
assess a HyperShift hosted control plane and its node pools
cluster
policy_compliance_report
summarize governance compliance and prioritize what to fix
-
capacity_report
find clusters with headroom and clusters under pressure
-
rollout_status
track a ManifestWorkReplicaSet rollout across selected clusters
name, namespace
Quickstart (laptop, ~15 minutes)
Requirements: docker, kind, kubectl,
clusteradm, helm, Python 3.11+.
The deployment guide has install commands and the real-fleet path.
bash
git clone https://github.com/sandeepbazar/ocm-mcp-server.git
cd ocm-mcp-server
make bootstrap # 1 hub + 3 managed kind clusters, OCM, Kyverno, policies, demo app
make install # pip install -e ".[dev,tracing]"
Configuration
The server is configured entirely through environment variables. The two that matter most
are kubeconfig context names. New to those? The
context names guide explains what they are and the exact
commands to find yours, from a laptop kind cluster to a cloud login. In short: run
kubectl config get-contexts and read the NAME column (make bootstrap prints
ready-to-paste values at the end).
Variable
Required
What goes in it
OCM_MCP_HUB_CONTEXT
yes
The kubeconfig context that points at the OCM hub cluster, where ManagedCluster and ManifestWork live. After make bootstrap this is kind-hub. Empty = current context.
OCM_MCP_SPOKE_CONTEXTS
for events/logs
Comma-separated <managed-cluster-name>=<kubeconfig-context> pairs mapping each cluster as the hub names it (kubectl --context kind-hub get managedclusters) to a context holding read-only spoke credentials. Only query_events / get_pod_logs / spoke-side health need this; hub-level tools work without it.
KUBECONFIG
no
Kubeconfig file path(s); defaults to ~/.kube/config.
OTEL_EXPORTER_OTLP_ENDPOINT
no
Set (e.g. http://localhost:4318) to emit a trace span per tool call. Unset = tracing off, audit log still on.
Path to the private Ed25519 signing key. Point this off the server (a separate account/device) so a compromised server cannot mint tokens. Default OCM_MCP_HOME/approval_ed25519.
OCM_MCP_VERIFIER_KEY
no
Path to the public verifier key the server loads. Mount read-only. Default OCM_MCP_HOME/approval_ed25519.pub.
OCM_MCP_ISSUER / OCM_MCP_AUDIENCE
no
Bind approval tokens to this deployment so a token minted elsewhere is refused. Defaults ocm-mcp / ocm-mcp-server.
OCM_MCP_APPROVAL_TTL
no
Approval-token lifetime in seconds. Default 3600.
OCM_MCP_REQUIRE_DIGEST
no
Set to 1 to require @sha256 digest-pinned images (stricter than tag-pinning). Default off.
OCM_MCP_METRICS_PORT
no
If set, expose Prometheus metrics at /metrics on this port. Default off.
OCM_MCP_METRICS_HOST
no
Interface the metrics endpoint binds. Default 127.0.0.1 (localhost only); set 0.0.0.0 for a remote scraper.
OCM_MCP_AUDIT_ECHO
no
Set to 1 to also echo each audit line to stderr as JSON, so a container log collector can forward the audit stream to a SIEM. Default off.
OCM_MCP_MAX_PROPOSAL_BYTES
no
Reject a proposal larger than this many bytes. Default 262144 (256 KiB).
OCM_MCP_MAX_HPA_REPLICAS
no
Reject a HorizontalPodAutoscaler whose maxReplicas exceeds this. Default 100.
OCM_MCP_READ_ONLY
no
Set to 1/true for a strictly-inspection deployment: every propose/apply tool refuses, a coarse backstop under the token gate. Default off.
OCM_MCP_CLIENT_TTL
no
Seconds before the cached Kubernetes API client is rebuilt, so rotated/refreshed credentials are picked up. Default 600.
OCM_MCP_SPOKE_TIMEOUT
no
Read timeout (seconds) for spoke health/event/log calls, so one large cluster cannot hang a tool. Default 30.
OCM_MCP_HEALTH_LIMIT
no
Max pods/deployments get_cluster_health fetches per cluster; the result notes truncation. Default 500.
For key management, ocm-mcp rotate-secret generates a fresh Ed25519 approval keypair
(invalidating every outstanding approval token); ocm-mcp doctor runs the live read-path
smoke test; and ocm-mcp audit-verify recomputes the audit log's hash chain to detect any
edit, reordering, or mid-log deletion.
bash
# the values make bootstrap prints, spelled out:export OCM_MCP_HUB_CONTEXT=kind-hub # context of the hub clusterexport OCM_MCP_SPOKE_CONTEXTS=cluster1=kind-cluster1,cluster2=kind-cluster2,cluster3=kind-cluster3
# └ name on the hub ┘ └ kubeconfig context with read-only creds ┘
Not sure where kind-hub or cluster1=kind-cluster1 come from, or what your own values
should be? The context names guide walks through it
step by step, including cloud logins (EKS, GKE, AKS, OpenShift). Pointing at a real
fleet instead of kind? Same variables; the deployment guide covers
the read-only spoke accounts and production hardening.
Then hand the server to your agent: the ready-made client configs are in
Connect your agent near the top of this README.
Evaluation harness: honest numbers
eval/ ships 22 scripted incident scenarios in three classes: remediate (15),
diagnose-only (3), adversarial (4). Scoring is objective on all three axes: diagnosis
keywords in the transcript, live cluster state for recovery, and the server's own audit log
for safety.
bash
python3 eval/run_eval.py --agent-cmd "claude -p"# or any agent CLI
Run it against your model of choice and publish your numbers, including the failures.
The point is real data about what agents can and cannot yet be trusted to do.
The Kyverno policies have their own offline test suite: make policy-test runs 16 CLI
cases (deploy/policies/tests/) against good, bad, and
human-created ManifestWorks with no cluster and no dependencies. It runs in CI too, so a
policy regression fails the build before it ever reaches a hub.
Try it end to end (one command)
Want proof it works against real clusters, not mocks? One script stands up a real
Open Cluster Management fleet on kind, exercises every tool and prompt, runs a
break-then-fix scenario, and writes a graphical HTML report:
bash
./hack/e2e-local.sh # 2 spokes, auto-cleanup (SPOKES=1 for a lighter run)
It (1) installs or version-checks the dependencies (Podman, kind, kubectl, clusteradm,
helm; Docker is not required), (2) bootstraps a hub plus spokes, (3) runs the read
tools, the gated propose -> approve -> apply write flow, a lifecycle action, and the
prompts - each with a plain-language explanation of what it does and why, (4) injects a
failing rollout and shows the diagnose-and-fix loop end to end, then (5) writes
e2e-report.html and tears the fleet back down (kind and Podman stay installed). The
report is git-ignored. Works on macOS (Homebrew + Podman) and Linux.
ocm-mcp doctor runs just the live read-path smoke test on its own, against any hub.
This project is independently maintained. If your organization wants priority integration
help, a hardened deployment review, sponsored features, or talks and workshops on safe
agentic operations, connect on
LinkedIn (details in SUPPORT.md).
Author
Sandeep Bazar - Passionate in Technology especially around Multi-cluster Kubernetes platforms, day-2
operations, and making fleets safer to automate.
If this project is useful to you, a ⭐ helps others find it.
Project governance and maturity
This project holds itself to CNCF community, governance, and security practices as a quality
bar - the same standards expected of a CNCF Sandbox project - so it is easy to adopt,
contribute to, and trust. The scaffolding is in place:
Governance - how decisions are made and how to become a maintainer.
Maintainers and Adopters - who maintains it, and an open page to record who uses it.