🛡️ ocm-mcp-server

MCP serverCloud & infra

Guardrailed fleet ops for AI agents: multi-cluster Kubernetes via OCM with policy, approval, audit.

Unavailable. This server has no hosted endpoint yet, so ahel can't serve it.

Connect ahel once, and every AI you use reads what you have installed.

From the project's README

As published by ocm-mcp-server/ocm-mcp-server in README.md.

📖 Read the docs site → ocm-mcp-server.github.io

AgentOps for Kubernetes fleets, done safely.

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.

📦 Get it  ·  ✨ Why  ·  🔌 Connect your agent  ·  🧭 Architecture  ·  🧰 Toolsets  ·  🛠️ Tools  ·  💬 Prompts  ·  🔭 Observability  ·  🚀 Quickstart  ·  📖 Wiki  ·  📚 Docs

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.


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.ocm-mcp-server/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/ocm-mcp-server/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.

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:

#LayerEnforced byWhat it stops
1Static checksthis server, before anything elseprivileged pods, host access, system namespaces, unpinned images, disallowed kinds
2Policy admissionKyverno dry-run on the hubanything your org's policies reject, evaluated inside the ManifestWork envelope
3Human approvalEd25519 token signed by ocm-mcp approve on a trusted terminal; the server needs only the public verifier keyany change reaching a cluster without a person consenting to that exact content and operation (one-time token, bound to content + operation + issuer/audience + expiry)
4Least-privilege RBACKuberneteseverything 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.

A write is two calls with a person between them. The token is bound to one content hash and one operation, it expires on its own, and offered a second time it is refused.

Two writes, the same four gates. The privileged, unpinned one dies at Layer 1 and never reaches a cluster; the compliant one waits for a person to sign the exact content, then lands and is verified.

A fleet operator's day with Claude, live from a cold start: install from PyPI, claude mcp add, inventory the fleet, reason about placement — then ship a new service the gated way: the privileged :latest shortcut is refused, the pinned proposal is signed by a human, applied with the token, verified, and the whole day is read back from the audit trail. — narrated MP4 · terminal cast.

The same day, driven by four different agents. Identical ten chapters, identical server - only the agent asking changes, which is the whole point of speaking MCP rather than shipping a client. Codex · Gemini (Antigravity CLI). Re-record any of them with hack/demo-record.sh all.

Connect your agent - any MCP client works

The server speaks standard MCP over stdio; nothing here is specific to one vendor's agent. That claim is demonstrated, not asserted: the same ten-chapter operator session is recorded against four different agents - Claude Code, Codex, Gemini through the Antigravity CLI, and IBM Bob Shell - driving the same server against the same fleet, each one really calling the tools, hitting the guardrail refusal, and applying only with a human-signed token. Re-record any of them with hack/demo-record.sh all.

Ready-made configs live in examples/ - see the index for where each file goes:

{
  "mcpServers": {
    "ocm-fleet": {
      "command": "ocm-mcp-server",
      "env": {
        "OCM_MCP_HUB_CONTEXT": "kind-hub",
        "OCM_MCP_SPOKE_CONTEXTS": "cluster1=kind-cluster1,cluster2=kind-cluster2,cluster3=kind-cluster3"
      }
    }
  }
}
{
  "servers": {
    "ocm-fleet": {
      "type": "stdio",
      "command": "ocm-mcp-server",
      "env": {
        "OCM_MCP_HUB_CONTEXT": "kind-hub",
        "OCM_MCP_SPOKE_CONTEXTS": "cluster1=kind-cluster1,cluster2=kind-cluster2,cluster3=kind-cluster3"
      }
    }
  }
}

Note the top-level key is servers, not mcpServers - VS Code differs from Claude Code and Gemini CLI here, and copying one into the other fails silently.

[mcp_servers.ocm-fleet]
command = "ocm-mcp-server"

[mcp_servers.ocm-fleet.env]
OCM_MCP_HUB_CONTEXT = "kind-hub"
OCM_MCP_SPOKE_CONTEXTS = "cluster1=kind-cluster1,cluster2=kind-cluster2,cluster3=kind-cluster3"
{
  "mcpServers": {
    "ocm-fleet": {
      "command": "ocm-mcp-server",
      "env": {
        "OCM_MCP_HUB_CONTEXT": "kind-hub",
        "OCM_MCP_SPOKE_CONTEXTS": "cluster1=kind-cluster1,cluster2=kind-cluster2,cluster3=kind-cluster3"
      }
    }
  }
}
{
  "mcpServers": {
    "ocm-fleet": {
      "command": "ocm-mcp-server",
      "env": {
        "OCM_MCP_HUB_CONTEXT": "kind-hub",
        "OCM_MCP_SPOKE_CONTEXTS": "cluster1=kind-cluster1,cluster2=kind-cluster2,cluster3=kind-cluster3"
      }
    }
  }
}

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:

make inject SCENARIO=failing-rollout CLUSTER=cluster2

You: "Payments is degraded somewhere in the fleet. Investigate and fix."

Agent: list_clustersget_cluster_health(cluster2)query_eventsget_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.

Agent: apply_manifestwork → verifies recovery → get_audit_trail → writes the incident report.

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

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 9 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. They are usable on their own: deploy/policies/README.md documents the foreach-over-embedded-manifests pattern, the two identifiers an adopter changes, and the Kyverno versions the pack is actually tested against.
  • make policy-test runs a 42-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 37 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.

ToolsetWhat it coversToolsWrites
inventoryManagedClusters, ClusterSets, set bindings, ClusterClaims, ManagedClusterInfo6-
observabilitycluster health, one-call fleet sweep, events, pod logs4-
placementPlacements, PlacementDecisions, AddOnPlacementScores3-
workManifestWork status feedback + the gated deploy and rollback flow9gated
addonsClusterManagementAddOns, fleet + per-cluster add-on health3-
registrationpending join CSRs + gated cluster lifecycle actions3gated
policygovernance compliance + violations rollup (if the add-on is installed)2-
hosted-control-planesHyperShift HostedClusters and NodePools (when the hub hosts them)3-
resourcesgeneric get/list over an allow-list of OCM API types2-
auditpending proposals, this server's own audit trail2-

The whole surface at once. Eight toolsets cannot change anything at all; the two that can are the two wearing a lock.

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.

The two lanes, to scale: a read answers straight away, a write crawls through propose, a human signature, and a one-time-token apply.

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

Shortened here. Read the whole README on GitHub.

Signals

GitHub stars
38
Forks
11
Last commit
Sep 2026
Advanced
Delivery
ocm-mcp-server MCP server → your ahel gateway (mcp.ahel.ai) → every connected AI client.
Catalog kind
mcp-server
Gateway key
io-github-ocm-mcp-server-ocm-mcp-server
Source
github.com/ocm-mcp-server/ocm-mcp-server