diff --git a/docs/MCP.md b/docs/MCP.md index 2e432ff..fa302a8 100644 --- a/docs/MCP.md +++ b/docs/MCP.md @@ -6,7 +6,7 @@ The trivy-operator-explorer **frontend** ships a built-in [Model Context Protoco Every read tool (`list_images`, `get_image`, `list_cves`, `list_images_with_cve`) accepts an optional `cluster` input. Set it to a cluster name to scope results to that cluster, or omit it to aggregate across all clusters in the bucket. The ignore-list tools are global (not cluster-scoped), matching the sqlite schema. -The tools are intentionally primitives (`list_images`, `get_image`, `list_cves`, `list_images_with_cve`, `list_ignored_cves`, `ignore_cves`, `unignore_cves`) rather than task-specific helpers. An LLM client composes them to answer ad-hoc questions; worked examples are at the bottom of this page. +The tools are intentionally primitives (`list_clusters`, `list_images`, `get_image`, `list_cves`, `list_images_with_cve`, `list_ignored_cves`, `ignore_cves`, `unignore_cves`) rather than task-specific helpers. An LLM client composes them to answer ad-hoc questions; worked examples are at the bottom of this page. ## Configuration @@ -52,6 +52,10 @@ curl -sS -H 'Content-Type: application/json' \ Each tool's full input/output JSON Schema is published over the MCP `tools/list` method. The summary below is the same `description` field returned in that listing. +### `list_clusters` (read) + +List the cluster names whose trivy-operator reports are currently available. Call this first in a multi-cluster deployment, then pass a returned name as the optional `cluster` argument on the other read tools to scope results to that cluster (omit it to aggregate across all clusters). Takes no inputs; returns `{total, clusters}`. + ### `list_images` (read) List every container image known to the cluster (scanned by trivy-operator plus any unscanned images detected via running pods). Returns per-image summary with vulnerability counts grouped by severity, fix-available counts, OS metadata, and the workloads that use the image. Mirrors the filters available at `/api/v1/images`. diff --git a/internal/mcp/server.go b/internal/mcp/server.go index a53b2f9..3b2c305 100644 --- a/internal/mcp/server.go +++ b/internal/mcp/server.go @@ -62,12 +62,19 @@ func newServer() *mcpsdk.Server { const serverInstructions = `Trivy Operator Explorer MCP server. Read-only and mutating tools for working with the vulnerability reports -produced by Aqua Security's trivy-operator in a Kubernetes cluster, plus a -persisted "ignored CVE" list. +produced by Aqua Security's trivy-operator across one or more Kubernetes +clusters (collected into a shared S3 bucket), plus a persisted "ignored CVE" +list. -Use the read tools (list_images, get_image, list_cves, list_images_with_cve, -list_ignored_cves) to inspect findings, then use the mutating tools -(ignore_cves, unignore_cves) to manage the ignore list per image. +Use the read tools (list_clusters, list_images, get_image, list_cves, +list_images_with_cve, list_ignored_cves) to inspect findings, then use the +mutating tools (ignore_cves, unignore_cves) to manage the ignore list per image. + +Reports come from many clusters. Call list_clusters first to discover the +available cluster names, then pass one as the optional "cluster" argument on +list_images/get_image/list_cves/list_images_with_cve to scope results to that +cluster. Omit "cluster" to aggregate across all clusters. The ignore list is +global (not cluster-scoped). Every Vulnerability returned from list_images, get_image, list_cves, and list_images_with_cve includes the trivy classification fields "class" (e.g. diff --git a/internal/mcp/tools.go b/internal/mcp/tools.go index 9afeb57..7547546 100644 --- a/internal/mcp/tools.go +++ b/internal/mcp/tools.go @@ -22,6 +22,13 @@ const defaultRegistry = "index.docker.io" // declared via mcp.AddTool so the SDK auto-generates input/output JSON // Schemas from the Go types. func registerTools(s *mcpsdk.Server) { + mcpsdk.AddTool(s, &mcpsdk.Tool{ + Name: "list_clusters", + Description: "List the cluster names whose trivy-operator reports are currently available. " + + "Use a returned name as the optional `cluster` argument on the other tools to scope " + + "results to a single cluster; omit `cluster` to aggregate across all of them.", + }, listClustersTool) + mcpsdk.AddTool(s, &mcpsdk.Tool{ Name: "list_images", Description: "List every container image known to the cluster (scanned by trivy-operator " + @@ -79,6 +86,27 @@ func registerTools(s *mcpsdk.Server) { }, unignoreCVEsTool) } +// ----- list_clusters ----- + +// listClustersParams intentionally has no fields; list_clusters takes no input. +type listClustersParams struct{} + +// listClustersResult wraps the cluster-name slice in an object so the +// auto-generated MCP output schema has type "object", which the spec requires +// for structured tool output. +type listClustersResult struct { + Total int `json:"total"` + Clusters []string `json:"clusters"` +} + +func listClustersTool(_ context.Context, _ *mcpsdk.CallToolRequest, _ listClustersParams) (*mcpsdk.CallToolResult, listClustersResult, error) { + clusters := source.ListClusters() + if clusters == nil { + clusters = []string{} + } + return nil, listClustersResult{Total: len(clusters), Clusters: clusters}, nil +} + // ----- list_images ----- type listImagesParams struct {