Hand-on test of radarhq.io K8S UI and dashboard on a macOS with Minikube and Podman
Kubernetes dashboards are often either overloaded with unnecessary complexity or too minimalist to provide deep operational context during an outage. Radar (radarhq.io) takes a refreshingly modern approach. It not only delivers a clean visual cluster dashboard but also natively integrates a Model Context Protocol (MCP) server.
By exposing cluster state β deployments, pods, topology, live operational issues, and logs β via MCP, Radar allows external clients, CLI tools, and AI agents to programmatically query and analyze Kubernetes workloads without direct, high-privilege access to the raw Kubernetes API.
In this blog post, I explore an end-to-end testing environment for Radar. I tested the overall system architecture, set up a sample Go workload (hello-k8s), inspect a Fyne-based GUI desktop client, and run an autonomous 7-step ReAct agent that performs cluster diagnostics over MCP.
I used IBM Bob SDLC for the implementation.
To test Radar locally on macOS without Docker Desktop, I leveraged Minikube powered by the Podman driver and CRI-O runtime.
Because Minikubeβs Podman driver places the VM inside an AppleHV VM whose internal IP (192.168.49.x
) is not directly routed to the host machine, I implemented background kubectl port-forward tunnels to bridge the local host to the internal cluster services:
localhost:30928
β Radar Pod (:9280
)localhost:30800
β hello-k8s
Pod (:8080
)hello-k8s
)
To give Radar and our MCP clients a realistic workload to inspect, a a lightweight HTTP application written in Go is deploye. The application tracks request counts, renders pod metadata obtained via the Kubernetes Downward API, and exposes health probes at /healthz
.
k8s/hello-k8s.yaml
) A 2-replica deployment is set-up, so that Radar can construct a multi-pod service topology graph:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-k8s
namespace: hello-k8s
labels:
app: hello-k8s
spec:
replicas: 2
selector:
matchLabels:
app: hello-k8s
template:
metadata:
labels:
app: hello-k8s
spec:
containers:
- name: hello-k8s
image: hello-k8s:latest
imagePullPolicy: Never
ports:
- name: http
containerPort: 8080
env:
- name: PORT
value: "8080"
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 3
periodSeconds: 5
resources:
requests:
cpu: 10m
memory: 16Mi
limits:
cpu: 100m
memory: 64Mi
---
apiVersion: v1
kind: Service
metadata:
name: hello-k8s
namespace: hello-k8s
spec:
type: NodePort
selector:
app: hello-k8s
ports:
- name: http
port: 8080
targetPort: 8080
nodePort: 30800
Rather than accessing raw Kubernetes endpoints directly, the custom GUI client communicates purely over HTTP JSON-RPC using the Model Context Protocol (MCP) endpoint provided by Radar at /mcp
.
The client uses the official @modelcontextprotocol/go-sdk
to establish stateless HTTP transport sessions and execute tools like get_dashboard and issues.
gui-client/main.go
)
func (c *MCPClient) callTool(ctx context.Context, toolName string, args map[string]any) ([]byte, error) {
client := mcp.NewClient(&mcp.Implementation{
Name: "radar-mcp-gui",
Version: "1.0.0",
}, nil)
β
transport := &mcp.StreamableClientTransport{
Endpoint: c.endpointURL,
}
β
session, err := client.Connect(ctx, transport, nil)
if err != nil {
return nil, fmt.Errorf("connect to %s: %w", c.endpointURL, err)
}
defer session.Close()
β
result, err := session.CallTool(ctx, &mcp.CallToolParams{
Name: toolName,
Arguments: args,
})
if err != nil {
return nil, fmt.Errorf("call %q: %w", toolName, err)
}
β
var combined []byte
for _, content := range result.Content {
if tc, ok := content.(*mcp.TextContent); ok {
combined = append(combined, []byte(tc.Text)...)
}
}
return combined, nil
}
To keep the UI responsive, a background goroutine fetches data snapshots on every poll interval (e.g., 10s) and passes updates over a buffered channel to the main thread:
// Background fetch loop
snapCh := make(chan ClusterSnapshot, 1)
β
go func() {
ctx := context.Background()
for {
snap := fetchSnapshot(ctx, mcpClient)
select {
case snapCh <- snap:
default: // Drop if UI thread is busy
}
time.Sleep(cfg.PollInterval)
}
}()
To automate workload inspection, a Go CLI agent (radar-agent
) that acts as an autonomous operator is implemented. It runs a deterministic 7-step ReAct (Reason + Act) loop using Radar's MCP tool set.
agent/main.go
) Here is an excerpt showing how the agent executes the observation phase and lists resources:
func run() error {
flag.Parse()
client := newMCPClient()
ctx := context.Background()
β
// Step 1: OBSERVE cluster health
step(1, "OBSERVE", "cluster overview via get_dashboard")
dash, err := stepObserve(ctx, client, *flagNamespace)
if err != nil {
return fmt.Errorf("observe: %w", err)
}
β
// Step 2: FOCUS on targeted pods
step(2, "FOCUS", fmt.Sprintf("list pods in namespace %q", *flagNamespace))
pods, err := stepListPods(ctx, client, *flagNamespace)
if err != nil {
fmt.Printf(" Warning: %v\n", err)
}
β
// Step 3: INSPECT Deployment spec/status
step(3, "INSPECT", fmt.Sprintf("get_resource deployment/%s", *flagWorkload))
dep, _ := stepInspectDeployment(ctx, client, *flagNamespace, *flagWorkload)
β
// Step 4: TOPOLOGY graph analysis
step(4, "TOPOLOGY", fmt.Sprintf("get_topology namespace=%q", *flagNamespace))
topo, _ := stepTopology(ctx, client, *flagNamespace)
β
// Step 5: Live operational ISSUES
step(5, "ISSUES", fmt.Sprintf("issues namespace=%q", *flagNamespace))
issues, _ := stepIssues(ctx, client, *flagNamespace)
β
// Step 6: Sample LOGS
step(6, "LOGS", fmt.Sprintf("get_workload_logs deployment/%s", *flagWorkload))
logs, _ := stepLogs(ctx, client, *flagNamespace, *flagWorkload)
β
// Step 7: Print Final Summary
step(7, "REPORT", "structured summary")
// ... renders formatted report to stdout ...
return nil
}
To test the full stack on your local machine, follow these steps:
Run the setup script to initialize Minikube, deploy Radar via Helm, build hello-k8s
, apply RBAC patches, and start port-forwarding:
chmod +x scripts/*.sh
./scripts/deploy.sh
./scripts/run-agent.sh --namespace hello-k8s --workload hello-k8s
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
RADAR AGENT REPORT
Goal : Investigate the hello-k8s workload and summarise its health
Namespace : hello-k8s
Workload : hello-k8s
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β
CLUSTER OVERVIEW
Health: healthy (cluster: minikube v1.28.0)
Pods: healthy=4 warning=0 error=0
Nodes: total=1 ready=1 notReady=0
Total problems: 0
β
PODS IN NAMESPACE "hello-k8s"
hello-k8s-74b884988f-2k9ll Running ready=1/1 node=minikube
hello-k8s-74b884988f-b98x7 Running ready=1/1 node=minikube
β
DEPLOYMENT: hello-k8s
Replicas: 2 desired / 2 ready / 2 available
Container: hello-k8s β hello-k8s:latest
β
TOPOLOGY (namespace "hello-k8s")
3 nodes, 2 edges
β
LIVE ISSUES
No issues found β workload looks healthy.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
./scripts/start.sh
| Technical Decision | Rationale |
| ------------------------------------ | ------------------------------------------------------------ |
| **Podman Driver + CRI-O** | Enables containerized Kubernetes testing on macOS without relying on Docker Desktop. MD |
| **`kubectl port-forward` Tunneling** | Solves AppleHV VM network isolation by routing `localhost:30928` directly to Radar's container port. MD |
| **Chart Tag Pinning (`1.7.0`)** | Prevents version mismatch crashes with Helm flags present in newer chart versions. MD |
| **RBAC Supplemental Patch** | Adds missing `rbac.authorization.k8s.io` read permissions required for Radar's permission inspection panels to function properly. MD |
| **Model Context Protocol (MCP)** | Decouples direct Kubernetes API access from monitoring tools, allowing lightweight agents and GUIs to consume structured cluster context safely. MD+ 1 |
By exposing cluster telemetry and operational controls through the Model Context Protocol, Radar transforms Kubernetes monitoring from a manual dashboard-checking task into a programmatic foundation for automation. Whether powering custom desktop GUIs like Fyne or enabling autonomous ReAct agents to run multi-step diagnostic workflows, MCP bridges the gap between raw cluster metrics and intelligent operational tools. As container environments continue to grow in complexity, decoupling cluster context from high-privilege API access via MCP offers a cleaner, safer, and far more extensible approach to Kubernetes management.
Thanks for reading π