Skip to content

Installing ray-mcp into an AI agent

This guide walks through building ray-mcp and connecting it to an MCP-capable AI agent (Claude Desktop, Claude Code, Cursor, or any MCP client). It also shows how to verify the connection by hand.

Status: v0.1.0 preview. The server exposes 19 tools across three permission tiers — read (always on), write (--allow-mutations), and destructive (--allow-destructive) — covering RayClusters, RayJobs, and RayServices. It runs over both the stdio transport (this guide) and a streamable HTTP transport with bearer-token / TokenReview auth (see HTTP transport & auth). By default it is read-only. The full, always-in-sync tool list is the Tool reference.

1. Install the binary

Fastest — install straight from the module with Go (no clone):

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

This drops ray-mcp in $(go env GOBIN) (or $(go env GOPATH)/bin). Get its absolute path for the agent config below:

echo "$(go env GOBIN)/ray-mcp"      # if GOBIN is set
echo "$(go env GOPATH)/bin/ray-mcp" # otherwise

Or build from a clone:

git clone https://github.com/risjai/ray-mcp.git
cd ray-mcp
go build -o ray-mcp ./cmd/ray-mcp     # produces ./ray-mcp
echo "$(pwd)/ray-mcp"                  # absolute path for the agent config

Requires Go 1.26.3+ (see go.mod).

2. Flags you'll likely set

Flag Default What it does
--default-namespace <ns> in-cluster ns, else default namespace used when a tool omits one
--allow-all-namespaces off serve every namespace instead of just --default-namespace; see note below
--context <name> current kubeconfig context which kubeconfig context to bind
--kubeconfig <path> $KUBECONFIG / discovery credentials source
--allow-mutations off register write tools (create/update/scale/submit/deploy)
--allow-destructive off register destructive tools (delete) — also needs --allow-mutations
--allow-raw-spec on expose the rawSpec power-user escape hatch on write tools; set --allow-raw-spec=false to prune it from the schemas
--log-level <level> info debug/info/warn/error (logs go to stderr)

Read tools are always on. By default the server is read-only — you must opt into mutations explicitly. Full flag reference: Configuration (and design spec §9). To serve over HTTP instead of stdio, see HTTP transport & auth.

--allow-all-namespaces is reconciled against your RBAC at boot. On startup the server runs a SelfSubjectAccessReview for cluster-wide list on the Ray CRDs (rayclusters, rayjobs, rayservices.ray.io). If the ServiceAccount is not authorized cluster-wide, the server refuses to start (grant a ClusterRole covering all three, or drop the flag) rather than lying about its scope. If the cluster can't be reached to run the check, it downgrades to namespaced scope (a warning to stderr) so an offline boot still works. ray_capabilities reports the reconciled truth: allNamespaces and servedNamespaces. With the flag off, passing allNamespaces: true to a list tool is refused at the edge.

The server always boots, even with no reachable cluster — so your agent always connects and ray_capabilities always works. The cluster connection is made lazily, on the first ray_cluster_* call; if the kubeconfig can't reach a cluster, those tools return a clean error (and retry once it's fixed — no restart needed). Point --context/--kubeconfig at a context where kubectl --context <name> get rayclusters works.

3. Connect your agent

MCP clients launch the server as a subprocess and speak JSON-RPC over its stdin/stdout. You give the client a command + args.

Claude Desktop

Edit claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json):

{
  "mcpServers": {
    "ray-mcp": {
      "command": "/absolute/path/to/ray-mcp",
      "args": ["--default-namespace", "default"]
    }
  }
}

Restart Claude Desktop. ray-mcp should appear in the tools (🔌) menu, exposing ray_capabilities.

Claude Code

claude mcp add ray-mcp /absolute/path/to/ray-mcp --default-namespace default

Then in a session, ask the agent to "call ray_capabilities" — or run /mcp to see it listed.

Cursor / other MCP clients

Use the same shape — a stdio server with command = the binary path and args = your flags. Consult your client's MCP docs for the config file location.

4. Verify by hand (no agent needed)

You can drive the server directly to confirm it works before wiring an agent. Pipe JSON-RPC frames into it over stdio:

{ printf '%s\n' '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"cli","version":"0"}}}';
  sleep 0.4;
  printf '%s\n' '{"jsonrpc":"2.0","method":"notifications/initialized"}';
  printf '%s\n' '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"ray_capabilities","arguments":{}}}';
  sleep 0.4;
} | ./ray-mcp --default-namespace demo --allow-mutations 2>/dev/null

The last line is the tool result — structured + a human summary:

{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"ray-mcp 0.0.0-dev | context (current-context) | default-namespace demo | scope: namespaced demo | tiers: read,write | KubeRay tested v1.6.1"}],"structuredContent":{"allNamespaces":false,"defaultNamespace":"demo","enabledTiers":["read","write"],"kubeContext":"(current-context)","kubeRayTested":"v1.6.1","servedNamespaces":["demo"],"serverVersion":"0.0.0-dev"}}}

Note enabledTiers became ["read","write"] because we passed --allow-mutations; without it you'd see ["read"].

Troubleshooting

  • Agent shows no tools / "server failed to start" — run the binary yourself with the same flags; errors print to stderr. A bad --http-addr bind or an invalid flag value makes the server refuse to boot with an explanatory message.
  • Garbled output / protocol errors — under stdio, stdout is the JSON-RPC wire. Don't redirect anything else to the server's stdout. All logs already go to stderr by design.
  • Can't reach the cluster — that affects the cluster/job/service tools, not ray_capabilities, which is config-only and makes no live call. Verify kubectl --context <name> get rayclusters works.

What's next

To contribute, see Contributing.