Install
uv tool install janus-mcp-server # or: pipx install janus-mcp-server # from a checkout: uv sync
Full client recipes live in the quick start.
Set up least-privilege credentials
Strongly recommended. Run the server against a dedicated ServiceAccount, never cluster-admin.
kubectl create namespace janus-mcp kubectl apply -f rbac/janus-mcp-rbac.yaml # edit namespaces first # then create a kubeconfig context bound to the janus-mcp ServiceAccount token, # e.g. named limited-sa@prod-cluster
What the manifests grant, and what they deliberately omit, is broken down in least-privilege RBAC.
Configure
mkdir -p ~/.config/janus-mcp cp examples/config.yaml ~/.config/janus-mcp/config.yaml $EDITOR ~/.config/janus-mcp/config.yaml # context, namespaces, write_tools
The server refuses to start if the pinned context is missing from the kubeconfig, if required read permissions are absent, or, with --strict, if the credentials are over-privileged enough to read Secrets.
Register with an MCP client
Example: Claude Desktop, ~/Library/Application Support/Claude/claude_desktop_config.json.
{
"mcpServers": {
"kubernetes": {
"command": "uvx",
"args": ["janus-mcp", "serve", "--config", "/Users/me/.config/janus-mcp/config.yaml"]
}
}
}
VS Code, Codex CLI and Cursor recipes are in the quick start.
Approving writes
With an elicitation-capable client, approval cards render natively. Read the live state line before clicking Approve.
With other clients the write returns status=pending approval_id=<id>:
janus-mcp approvals # list pending requests janus-mcp approve <id> # approve one
Then tell the assistant to re-issue the call with the same arguments. Approvals expire (write_tools.oob_approval_ttl_seconds, default 10 minutes) and are burned on first use.
Scale approvals are additionally bound to the resourceVersion observed when the request was created. If the object changed while the approval waited, whether an HPA scaled it or a colleague did, the write aborts with a conflict instead of applying to state you never saw. Re-request and re-approve.
Audit
Every call is one JSONL record in ~/.local/state/janus-mcp/audit.jsonl, rotated at 10 MiB: timestamp, tool, identifier args, scope and approval decisions, redaction counts. Bodies are never logged.
jq 'select(.event=="write_approved")' ~/.local/state/janus-mcp/audit.jsonl
Troubleshooting
| Symptom | Cause and fix |
|---|---|
cannot load kubeconfig context at start | The pinned context does not exist. Check kubectl config get-contexts. |
missing permissions: list pods in … | Apply or extend the RBAC manifests for that namespace. |
| Over-privilege warning at start | You pointed it at an admin kubeconfig. Create the dedicated ServiceAccount. |
rate limit exceeded tool errors | Expected back-pressure. Raise limits.rate_per_minute deliberately if needed. |
truncated=true in results | Narrow with label_selector, tail_lines or since_minutes. Raise result_max_bytes only with the token budget in mind. |
| Writes missing from the client's tool list | read_only: true, or the tool is not listed in write_tools.enabled. |
| Works in the terminal, fails from a GUI client | GUI hosts spawn the server with a minimal PATH, so the kubeconfig's exec: auth plugin is not found. Use the plugin's absolute path in users[].user.exec.command. |
[REDACTED:high-entropy] over-firing on legitimate ids | Raise redaction.entropy_threshold slightly, for example to 4.8. Never disable the pass. |
Development
uv sync uv run pytest # unit + security suites (no cluster needed) uv run pytest tests/security # probing + frame-capture leak tests UPDATE_GOLDENS=1 uv run pytest tests/unit/test_goldens.py # regen, then REVIEW the diff uv run ruff check . && uv run ruff format --check . && uv run mypy
Integration tests against a real cluster:
kind create cluster --name janus-test JANUS_KIND_TEST=1 uv run pytest tests/integration -m integration