AI Integration (MCP)

August 6, 2026 · View on GitHub

Radar includes a built-in Model Context Protocol (MCP) server that lets AI agents query your Kubernetes cluster.

Why MCP instead of raw kubectl?

Giving an AI agent raw kubectl access has problems:

  • Token wastekubectl get pod -o yaml returns verbose YAML full of managed fields, status conditions, and metadata noise that burns through LLM context windows
  • No enrichment — raw output lacks topology relationships, health assessments, or cross-resource correlation
  • Write access risk — kubectl can modify and delete resources

Radar's MCP server solves these:

  • Token-optimized — resources are minified, stripping noise (managed fields, internal annotations, redundant status) while preserving what matters
  • Enriched data — topology graphs, health assessments, deduplicated events, filtered logs (prioritizing errors/warnings)
  • Safe operations — read tools are read-only (readOnlyHint); write tools (restart, scale, rollback, sync, apply, cordon/drain) are RBAC-enforced and annotated destructiveHint so AI clients can prompt for confirmation
  • Secret-safe — Secret data is never exposed, environment values are redacted, log output is scrubbed for API keys and tokens
  • RBAC-aware — respects your cluster's RBAC permissions
  • Vendor-neutral — works with any MCP-compatible AI tool

Enabling / Disabling

The MCP server is enabled by default when Radar starts. To disable it:

radar --no-mcp

MCP Endpoint

http://localhost:9280/mcp

The port matches your --port flag (default 9280). The MCP server uses HTTP transport with JSON-RPC.

Catalog Introspection

MCP registries and inspectors can start Radar without a Kubernetes cluster when they only need the tool and resource catalog:

radar --mcp-catalog-only --no-browser

This mode skips Kubernetes initialization and starts the /mcp endpoint for schema introspection. Cluster-backed tool calls still require a normal Radar process connected to Kubernetes.

For registries that launch MCP servers over stdio, use:

radar --mcp-catalog-stdio

This exposes the same tool and resource catalog over stdio without starting the HTTP UI server. The stdio mode is intended only for catalog introspection; normal Radar sessions use the HTTP endpoint above.

Setup Instructions

Connect your AI tool to Radar's MCP server. Radar must be running first (radar or kubectl radar).

Claude Code

Run this command:

claude mcp add radar --transport http http://localhost:9280/mcp

Claude Desktop

Add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "radar": {
      "type": "http",
      "url": "http://localhost:9280/mcp"
    }
  }
}

Cursor

Add to ~/.cursor/mcp.json:

{
  "mcpServers": {
    "radar": {
      "url": "http://localhost:9280/mcp"
    }
  }
}

Windsurf

Add to ~/.codeium/windsurf/mcp_config.json:

{
  "mcpServers": {
    "radar": {
      "serverUrl": "http://localhost:9280/mcp"
    }
  }
}

VS Code Copilot

Add to .vscode/mcp.json in your workspace:

{
  "servers": {
    "radar": {
      "type": "http",
      "url": "http://localhost:9280/mcp"
    }
  }
}

Cline

Add via the Cline MCP settings UI:

{
  "mcpServers": {
    "radar": {
      "url": "http://localhost:9280/mcp",
      "type": "streamableHttp"
    }
  }
}

JetBrains AI

Add via Settings > Tools > AI Assistant > MCP:

{
  "mcpServers": {
    "radar": {
      "url": "http://localhost:9280/mcp"
    }
  }
}

OpenAI Codex

Add to ~/.codex/config.toml:

[mcp_servers.radar]
url = "http://localhost:9280/mcp"

Gemini CLI

Add to ~/.gemini/settings.json:

{
  "mcpServers": {
    "radar": {
      "httpUrl": "http://localhost:9280/mcp"
    }
  }
}

MCP Registry / Docker

Radar is listed on the official MCP registry as io.github.skyhook-io/radar, packaged as the ghcr.io/skyhook-io/radar Docker image. The registry-suggested invocation is:

docker run -p 127.0.0.1:9280:9280 \
  -v ~/.kube/config:/kubeconfig:ro \
  -e KUBECONFIG=/kubeconfig \
  ghcr.io/skyhook-io/radar:latest

Prefer the native install (brew install skyhook-io/tap/radar, krew, or the install script — see the README) — it uses your kubeconfig exactly as kubectl does. The Docker path has real limitations because the image is distroless (no shell, no cloud CLIs):

  • Exec-plugin auth does not work. Kubeconfigs for GKE/EKS/AKS typically call gke-gcloud-auth-plugin, aws eks get-token, or kubelogin — none exist inside the container. Only kubeconfigs with embedded certificates or static tokens work.
  • Local clusters are unreachable. kind/minikube/Docker Desktop API servers listen on the host's 127.0.0.1, which inside the container is the container itself.
  • File ownership matters. On Linux hosts a 0600 kubeconfig owned by your user is unreadable by the container's nonroot (uid 65532) user.

The Docker image's primary use is in-cluster deployment with a ServiceAccount, where none of these apply.

Available Tools

Read Tools

ToolDescriptionParameters
issues"What's broken right now?" — a ranked, curated stream of live operational failures: failing workloads/pods, active native Helm release failures or stuck pending operations (kind=HelmRelease, group=helm.sh), dangling references, pod-startup blockers (unschedulable / admission-rejected / stuck post-bind), and False CRD conditions. No source filter; each row carries a source label sliceable via filter. Recovered Helm rollbacks are deploy history, not live issues; use get_changes for Helm deployment history and get_helm_release for native Helm full revision/history/hook diagnostics. Flux HelmRelease rows (group=helm.toolkit.fluxcd.io) are GitOps reconcilers and should use diagnose. For static posture use get_cluster_audit; for raw events use get_events.namespace (optional), severity (optional: critical,warning), kind (optional), filter (optional CEL), limit (optional, default 200, max 1000)
diagnoseRoot-cause one workload, GitOps reconciler, or network entry kind in a single call. Pod/Deployment/StatefulSet/DaemonSet get minified resource + resourceContext + current AND previous container logs across pods + crashCause evidence that pairs active crashloop status with one highest-signal filtered log line (logLineSelection states how the line was chosen, in descending confidence: fatal_pattern, traceback_header_only, last_matched_line, log_tail) + filtered events + startupBlockers; Application/Kustomization/Flux HelmRelease get reconciler status + related parsed issues; Service/Ingress/HTTPRoute/GRPCRoute/Gateway get a coverage-honest reachability diagnosis: a summary (headline + tested/passed/failed/skipped counts over the intended routes), routes (per-route outcome + confidence - indirect = reached only via the API-server proxy, not the live-traffic path), notTested (with why + a copyable command), a named brokenRoute, and path (hops with static findings). Optional active reachability test (probe: true, bounded to a 3s total budget; a timeout returns an honest partial). In resourceContext, issueSummary uses live-operational critical/warning, while auditSummary uses the Checks posture-remediation ladder critical/high/medium/low (current built-ins are high/medium) and is not evidence of an active outage.kind (required: workload, GitOps reconciler, or network entry kind), namespace (required), name (required), probe (optional: network kinds only - run DNS/TCP/TLS/HTTP probes as applicable; explicitly non-HTTP Service ports stop at TCP), inCluster (optional, MUTATING - network kinds only: runs the applicable probe layers from inside the cluster by creating up to 5 short-lived, self-deleting probe pods under your RBAC; the only diagnose option with a side effect, requires create-jobs + list/get pods RBAC; forces probe on)
get_dashboardCluster/namespace health overview — resource counts, failing pods, unhealthy workloads, warning-event groups (warningGroups, up to 20 recency-ordered; totalWarningGroups/warningGroupsTruncated signal when more exist), Helm status. Inventory-style triage before drilling in.namespace (optional)
top_resourcesLive metrics ranked like `kubectl topsort`, joined with K8s context (status, restarts, owner, requests/limits). Use for CPU/memory/OOM/load symptoms.
list_resourcesList resources of a kind with minified summaries + per-row summaryContext (managedBy / health / issueCount).kind (required), group (optional), namespace (optional), context (optional: default / none)
searchFind resources by content/term match (config keys, env refs, images, label values, CRD fields, status messages). Tokens AND'd; secret values never indexed. Supports kind:/ns:/label:/image: modifiers and CEL filter.query (required), filter (optional CEL), limit (optional)
get_resourceDetailed view of a single resource — minified spec + status + metadata + default-on resourceContext (managedBy / exposes / selectedBy / uses / runsOn / issue+audit rollups). issueSummary uses live-operational critical/warning; auditSummary uses the Checks posture-remediation ladder critical/high/medium/low (current built-ins are high/medium) and is not evidence of an active outage. Optionally include heavier supplemental data (events / metrics). For logs use get_pod_logs / get_workload_logs / diagnose.kind (required), namespace (optional — omit for cluster-scoped kinds: Node, ClusterRole, IngressClass, etc.), name (required), group (optional, for ambiguous kinds), include (optional: events,metrics), context (optional: basic default, none for bare minified output)
get_topologyWhole-namespace/cluster topology graph (nodes + edges). Use summary format for LLM-friendly text chains. Once you have a suspect root, prefer get_neighborhood.namespace (optional), view (optional: traffic or resources), format (optional: graph or summary)
get_neighborhoodBFS-expanded topology neighborhood around one known root — cheaper and clearer than get_topology for cross-resource failures (routing, selector/endpoint, refs, owner chains). RBAC-filtered.kind (required), namespace (optional), name (required), profile (optional: auto default / all), hops (optional, default 1, max 2)
get_eventsRecent Kubernetes events, deduplicated and sorted Warning-groups-first then by recency — all types by default, so warnings lead and lifecycle events follow as timeline evidence. Filter by resource kind/name to scope; type=Warning for warnings only, type=Normal for lifecycle only.namespace (optional), limit (optional, default 20, max 100), kind (optional), name (optional), type (optional: all default, Warning, Normal)
get_changesRecent meaningful changes from the Kubernetes cluster timeline plus native Helm release deployment/operation history (source: helm). Use to investigate what changed before an incident, including failed upgrades, rollbacks, and current Helm revisions. If the response includes sourcesErrored, treat it as partial data for those sources. Use get_helm_release include=history,operations for the full Helm revision trail.namespace (optional), kind (optional), name (optional), since (optional, e.g. 1h, 30m; default 1h), limit (optional, default 20, max 50)
get_pod_logsPod logs with secret redaction. Without grep, prioritizes errors/warnings and falls back to recent tail lines; with grep, returns only regex-matching lines instead of applying the diagnostic filter.namespace (required), name (required), container (optional), tail_lines (optional, default 200), grep (optional)
get_workload_logsAggregated logs from all pods of a workload (Deployment, StatefulSet, DaemonSet, Job, Argo Workflow). Without grep, auto-filters for diagnostic relevance; with grep, returns only matching timestamp-prefixed lines.kind (required), namespace (required), name (required), container (optional), tail_lines (optional, default 100 per pod), grep (optional)
get_cluster_auditStatic config posture — best-practice findings (Security / Reliability / Efficiency) with remediation. INDEPENDENT of operational health. Finding severity and the explicit summary.critical/high/medium/low counts are posture remediation priority; current built-in checks emit high or medium. For "what's broken right now?" use issues.namespace (optional), category (optional), severity (optional: critical, high, medium, or low)
list_packagesInstalled packages (Helm releases, label-managed workloads, CRDs, Argo Applications, Flux HelmReleases + Kustomizations) with source provenance, versions, and health, in one call. Response includes sourceLegend for the stable source codes.namespace (optional), source (optional: H/helm, L/labels, C/crds, A/argocd, F/fluxcd), chart (optional substring)
list_helm_releasesList Helm releases with status, resource health, storage namespace, Flux ownership, current lastOperation, and a capped operations trail when Helm history indicates failed upgrades, rollback-after-failure, rollbacks, or stuck pending operations. Use this first for Helm deployment debugging.namespace (optional)
get_helm_releaseDetailed Helm release info with owned resources, resource health, Flux ownership, current lastOperation, operationInsight (active/recovered state, likely resource to inspect, suggested compare), hooks, and failed/running hook diagnostics with live Job/Pod/Event/redacted-log evidence when still available. Use include=history,operations for the full Helm revision trail; include=values for key-aware redacted user values; include=diff,values_diff,notes_diff,resource_diff for revision comparison. For releases with storageNamespace, pass that value as namespace.namespace (required: Helm storage namespace), name (required), include (optional: values,history,operations,diff,values_diff,notes_diff,resource_diff), diff_revision_1 (required when include contains a diff token) / diff_revision_2 (optional, defaults to current)
list_namespacesList all namespaces with status(none)
get_subject_permissionsEffective RBAC permissions of a ServiceAccount / User / Group. Without verb/resource, returns the existing bindings, deduplicated flat rules, and (for SAs) Pods using it. With both fields, runs an authoritative SubjectAccessReview for a ServiceAccount and returns a focused allow/deny result; this requires the caller itself to have create subjectaccessreviews.authorization.k8s.io.kind (required: ServiceAccount, User, or Group), namespace (required for ServiceAccount; omit for User/Group), name (required), verb + resource (optional pair; ServiceAccount only), group, resource_namespace (defaults to subject namespace; explicitly empty for a cluster-scoped or cluster-wide API request, not aggregation of namespace-local grants), subresource, resource_name (optional check refinements)
query_prometheusExecute PromQL against the cluster's Prometheus (auto-discovered or --prometheus-url; works with PromQL-compatible backends: Thanos, VictoriaMetrics, Mimir). type=instant returns current values; type=range returns time-series history with automatic step adjustment. Empty results include a bounded list of related active metric names when a metric family can be inferred. Oversized results return a label-cardinality summary + suggested topk rewrite instead of raw data.query (required), type (optional: instant default, range), since (optional, e.g. 30m, 1h, 24h, 7d; default 1h), start / end (optional RFC3339, override since), step (optional, auto-calculated when omitted), max_points (optional, default 300, max 600), timeout (optional seconds, default 30, max 180)
discover_metricsDiscover exact metric names (enriched with type/help from Prometheus metadata) or values of one label before writing PromQL. Lists active series from the last hour; truncated: true means narrow the match selector.match (PromQL series selector; required when label is empty), label (optional: list values of this label instead of metric names), limit (optional, default 100, max 500)
get_prometheus_rulesList Prometheus alerting/recording rules with PromQL definitions, state, labels, annotations, and active alert instances. Alert-investigation entry point: fetch the rule definition, then run its query with query_prometheus.type (optional: alert, record), name / group (optional substring filters), state (optional: firing, pending, inactive), limit (optional, default 50, max 200)

Write Tools

ToolDescriptionParameters
apply_resourceCreate or update a Kubernetes resource from YAML. Supports multi-document YAML, per-document partial-failure results, server-side dry-run preview, and SSA ownership-conflict reporting.yaml (required), mode (optional: apply or create, default apply), dry_run (optional, default false), namespace (optional, override), verify (optional, default true: post-mutation state, submitted-vs-live diff, dry-run preview diff, workload rollout/pods, and related issues), force (optional, default false: take SSA field ownership from other managers)
patch_resourcePatch one existing Kubernetes resource with JSON Patch, JSON Merge Patch, or strategic merge patch. Use for precise field/list edits when you know the exact path and do not want to rewrite the full manifest or take broad server-side-apply ownership. Strategic patch is for built-in Kubernetes kinds and name-keyed list edits, such as changing one container.kind (required), name (required), namespace (required for namespaced resources), group (optional), patch_type (optional: json default, merge, or strategic), patch (required JSON string), dry_run (optional), verify (optional, default true: compact post-patch state, dry-run preview diff, and JSON Patch field checks)
manage_workloadRestart, scale, or rollback a Deployment, StatefulSet, or DaemonSet. Note: scale is not supported for DaemonSets.action (required: restart, scale, rollback), kind (required), namespace (required), name (required), replicas (for scale), revision (for rollback)
manage_cronjobTrigger, suspend, or resume a CronJobaction (required: trigger, suspend, resume), namespace (required), name (required)
manage_gitopsManage ArgoCD and FluxCD resources — sync, refresh, terminate, suspend, resume, rollback (Argo), reconcile (Flux), reconcile-with-source (Flux)action (required), tool (required: argocd or fluxcd), namespace (required), name (required), kind (FluxCD only). For sync: revision, prune, dry_run, force, apply_only, sync_options. For rollback (Argo only): history_id (required), prune, dry_run. Per-action input validation rejects flags that don't apply to the action (e.g. force on suspend) so callers fail loudly instead of silently.
manage_nodeCordon, uncordon, or drain a Kubernetes nodeaction (required: cordon, uncordon, drain), name (required), delete_empty_dir_data (optional, default true), force (optional), timeout (optional, seconds, default 60)

Available Resources

URIDescription
cluster://healthCluster health summary (same data as get_dashboard)
cluster://topologyFull cluster topology graph
cluster://eventsRecent warning events (up to 50)

Security

  • Safe by design — read tools are strictly read-only and annotated with readOnlyHint; write tools (restart, scale, rollback, sync, apply, cordon/drain) are RBAC-enforced and annotated with destructiveHint so AI clients can prompt for confirmation. Some are genuinely destructive — apply_resource force=true can take field ownership from Helm/Flux, manage_node drain evicts pods, and rollback/terminate overwrite or abort desired state
  • RBAC-aware — every call enforces RBAC at the same boundary as the REST API:
    • Local binary: the cache uses your kubeconfig identity, so MCP can only see what kubectl can see for that user
    • In-cluster (auth enabled): read tools intersect namespaced reads with the calling user's RBAC-allowed namespaces; cluster-scoped reads (Nodes, PVs, ClusterRoles, cluster-scoped CRDs) are gated per-kind via SubjectAccessReview, so cluster-wide pod visibility doesn't implicitly grant Node read; write tools, exec, and logs are fully impersonated so the apiserver enforces the user's RBAC end-to-end
    • In-cluster (no auth): every MCP caller shares the pod ServiceAccount's view — only deploy this way when MCP isn't exposed beyond a trusted boundary
  • Prometheus metric data is NOT namespace-filtered — PromQL cannot be namespace-scoped server-side (arbitrary queries can aggregate across namespaces), so query_prometheus and discover_metrics follow the same stance as the REST /prometheus/query endpoint: any authenticated user may run PromQL. Deploy with auth enabled when Prometheus contains sensitive label values
  • Secret redaction — Secret .data and .stringData are never exposed; only key names are shown
  • Value redaction — environment variable values and Helm values returned through MCP are scrubbed for known secret patterns; Helm values also use key-aware redaction for names like password, token, privateKey, and secretKey
  • Log redaction — pod log output and Helm hook log evidence are scrubbed for secret patterns before being returned