Skip to content
Conduit

mcp server

ray-mcp

MCP server for managing Ray Clusters and Jobs. Written in Go for native k8s support.

risjai/ray-mcpcommunitylistedunreviewed

Set up

Runs on your computer. Pick your agent and operating system.

This project does not publish an install method we can turn into a config. Follow the README instead.

README

synced Oct 27, 2026

ray-mcp

An MCP server for Ray on Kubernetes. Point Claude (or any MCP client) at your cluster and ask about your Ray workloads in plain language. Read-only today; a guarded write path is on the way (see below).

CI License Go Status Stars

📖 Documentation — install, the full tool reference (all 19 tools with examples), HTTP transport & auth, and architecture.

Status: v0.1.0 preview. Today: ray_capabilities, the RayCluster read tools (ray_cluster_list / ray_cluster_get / ray_cluster_events), the full guarded RayCluster write path (create / update / scale / delete), the read-only RayJob wedge (ray_job_get / ray_job_logs / ray_job_wait — CRD status fused with the live Ray dashboard/job API), and the RayJob write/list path (ray_job_submit / ray_job_list / ray_job_delete — delete is mode-aware: deleting an ephemeral job cascades to its cluster, so that path is destructive), the read-only RayService tools (ray_service_list / ray_service_get — distilled rollout phase + serve health), and the RayService write tools (ray_service_deploy / ray_service_update / ray_service_delete — update predicts the operator's reconfig path: in-place serve reconfig, zero-downtime cluster swap, or scale; delete is destructive-tier, refuses a service that appears to be serving traffic unless forced), over stdio.

Why ray-mcp

A generic Kubernetes MCP can kubectl get a RayCluster CRD and hand the agent raw YAML. ray-mcp is built specifically for Ray, with the LLM as the consumer.

What it does today (v0.1.0):

  • Distilled status, not raw YAML. ray_cluster_get returns a one-line health read — "Ready; 1/1 workers ready", derived from the cluster's conditions — not a wall of .status. ray_cluster_events surfaces why a cluster is stuck, Warnings first — e.g. a pod's FailedScheduling event with message "0/1 nodes available: insufficient nvidia.com/gpu" — instead of leaving the agent to dig.
  • Read-only and token-bounded by default. Safe to point at any cluster — no tool can mutate it. Output is compact and capped (small list rows, bounded event slices), because the consumer is an LLM with a finite context budget.
  • Guarded writes, opt-in by tier. The full RayCluster write path — create / update / scale / delete — ships behind --allow-mutations (destructive ops additionally need --allow-destructive), via Server-Side Apply with dry-run, before/after diffs, and a stateless confirm-fingerprint two-step on destructive ops. The ray-mcp/protected annotation refuses deletion, and writes only ever go through the guarded CRD path — the unauthenticated Ray dashboard is never a write vector.

On the roadmap (designed, not yet built):

  • The wedge — a read-only reach into Ray's dashboard/job API for live job status and logs, the runtime detail the CRDs don't hold (this is where the distillation above extends to jobs: "why is my job pending?").
  • RayService delete — the last RayService write op, layered on the guarded write path below (deploy / update already ship; the read tools too).

Install

go install github.com/risjai/ray-mcp/cmd/ray-mcp@latest   # or @v0.1.0 to pin

Requires Go 1.26.3+. This drops a ray-mcp binary in $(go env GOPATH)/bin (or $(go env GOBIN)). No Go? Build from a clone instead — see docs/INSTALL.md.

Connect it to your agent

ray-mcp speaks MCP over stdio — your agent launches it as a subprocess. The binary is a server; you don't run it directly (it'll just wait on stdin). Register it:

Claude Code

claude mcp add --scope user ray-mcp "$(go env GOPATH)/bin/ray-mcp" \
  -- --context <your-kube-context> --default-namespace <ns>

Claude Desktop — add to claude_desktop_config.json:

{
  "mcpServers": {
    "ray-mcp": {
      "command": "/absolute/path/to/ray-mcp",
      "args": ["--context", "<your-kube-context>", "--default-namespace", "<ns>"]
    }
  }
}

Cursor and other MCP clients use the same shape (command + args). Full details, flags, and a by-hand verification: docs/INSTALL.md.

What you can do today

Four read-only tools (the three cluster tools carry readOnlyHint):

ToolAsk your agent…
ray_cluster_list"list the Ray clusters in namespace team-a"
ray_cluster_get"is ray-sample healthy? how many workers are ready?"
ray_cluster_events"why is ray-sample stuck? show recent events"
ray_capabilities"what can this ray-mcp server do?" (needs no cluster)

A real ray_cluster_list result against a live cluster looks like:

{
  "clusters": [
    {"name": "ray-sample", "namespace": "default", "phase": "Ready",
     "ready": 1, "desired": 1, "ageSeconds": 122,
     "health": "Ready; 1/1 workers ready"}
  ],
  "count": 1, "moreAvailable": false
}

The server boots even with no reachable cluster — ray_capabilities always works; the cluster tools dial lazily on first use and return a clean error (then retry, no restart) if the kubeconfig can't reach a cluster.

Try it end-to-end (no Ray experience needed)

Don't have a Ray cluster yet? docs/TRY-IT-WITH-CLAUDE-CODE.md takes you from zero to asking Claude Code about a live cluster: spin up a local kind cluster, install KubeRay, create a sample Ray cluster, and connect ray-mcp. Needs Docker + kubectl + Go + Claude Code; ~20–30 min; fully disposable.

Learn more

Roadmap

FeatureStatus
ray_capabilities — server info, bound context, tiers✅ Shipped (v0.1.0)
RayCluster read — list / get (distilled status)✅ Shipped (v0.1.0)
RayCluster events — ray_cluster_events (Warnings-first)✅ Shipped (v0.1.0)
RayCluster create — ray_cluster_create (unified apply pipeline, SSA, dry-run, diffs)✅ Shipped (--allow-mutations)
RayCluster writes — update / scale (SSA, dry-run, diffs, autoscaler-safe)✅ Shipped (--allow-mutations)
RayCluster delete — ray_cluster_delete (destructive tier, two-step confirm-fingerprint, protected guard)✅ Shipped (--allow-destructive)
The wedge — read-only Ray dashboard/job API reach (live status)✅ Shipped
RayJob read — ray_job_get / ray_job_logs (two-phase wedge: CRD + live dashboard)✅ Shipped
RayJob wait — ray_job_wait (bounded ≤30s, until=running|terminal, two-phase)✅ Shipped
RayJob submit — ray_job_submit (non-blocking, existingCluster XOR ephemeral clusterSpec, dry-run, diffs)✅ Shipped (--allow-mutations)
RayJob list — ray_job_list (both statuses per row: CRD lifecycle + Ray phase)✅ Shipped
RayJob delete — ray_job_delete (mode-aware: ephemeral cascade is destructive + confirm-fingerprint, existing-cluster is a plain write, protected guard)✅ Shipped (--allow-mutations)
RayService read — ray_service_list / ray_service_get (distilled rollout phase + serve health)✅ Shipped
RayService writes — ray_service_deploy / ray_service_update (SSA, dry-run, diffs; update predicts the operator's reconfig path: in-place / zero-downtime-swap / scale)✅ Shipped (--allow-mutations)
RayService delete — ray_service_delete (destructive tier, two-step confirm-fingerprint, protected guard, refuses a serving service unless force=true; always cascades to the owned cluster)✅ Shipped (--allow-destructive)
Streamable HTTP transport + static bearer auth (--transport http, mandatory token on any non-loopback bind)✅ Shipped
HTTP auth — TokenReview mode (--auth-mode tokenreview, K8s SA tokens) + caller identity in the audit log (SA username, or a token fingerprint in static mode)1✅ Shipped
Namespace scope reconciliation — --allow-all-namespaces verified against RBAC at boot (SelfSubjectAccessReview); refuse-to-start on denial, downgrade when offline; ray_capabilities reports the served scope✅ Shipped
Read-only RBAC floor — ServiceAccount + ClusterRole (deploy/rbac/)✅ Shipped (read-only)
Helm chart + in-cluster Deployment📋 Planned
Prebuilt binaries + Homebrew (GoReleaser)📋 Planned

✅ Shipped · 🚧 Next (Phase 2, in progress) · 📋 Planned

Contributing & testing

PRs welcome. The fast test loop needs no Docker:

make test          # unit + dashboard + MCP tiers — fast, no Docker
make test-envtest  # KubeRay adapter against envtest (apiserver+etcd, no Docker)
make e2e           # real kind + KubeRay cluster (needs Docker + kind)

See CONTRIBUTING.md for the full workflow and what each tier proves. Licensed under Apache 2.0.

Footnotes

  1. v1 caveat: tokenreview mode validates tokens without an explicit audience, so the API server checks them against its own default audience — meaning any valid in-cluster ServiceAccount token is accepted (and audited under that SA's name), not only tokens minted for ray-mcp. The HTTP token gates reach, not privilege — the server's own SA RBAC is the real boundary — but until audience binding lands, restrict who can reach the listener with a NetworkPolicy. Enabling this mode also requires the server's ServiceAccount to hold create on tokenreviews (bind the built-in system:auth-delegator ClusterRole); it lands with the RBAC/Helm work. ↩

↑↓ navigate openesc close