From b251f21e2510b6e0d2f7a7c8cb767ce409977366 Mon Sep 17 00:00:00 2001 From: Fede Barcelona Date: Mon, 18 May 2026 10:49:30 +0200 Subject: [PATCH] build: update base images and go modules to fix vulns --- docker-base-aarch64.nix | 4 +- docker-base-amd64.nix | 4 +- flake.lock | 6 +- go.mod | 24 ++++---- go.sum | 56 +++++++++-------- .../infra/mcp/tools/tool_generate_sysql.go | 6 +- .../infra/mcp/tools/tool_get_event_info.go | 6 +- .../mcp/tools/tool_get_event_process_tree.go | 6 +- .../infra/mcp/tools/tool_k8s_list_clusters.go | 6 +- .../mcp/tools/tool_k8s_list_clusters_test.go | 43 +++++++------ .../tool_k8s_list_count_pods_per_cluster.go | 6 +- ...ol_k8s_list_count_pods_per_cluster_test.go | 43 +++++++------ .../infra/mcp/tools/tool_k8s_list_cronjobs.go | 6 +- .../mcp/tools/tool_k8s_list_cronjobs_test.go | 46 ++++++++------ .../infra/mcp/tools/tool_k8s_list_nodes.go | 6 +- .../mcp/tools/tool_k8s_list_nodes_test.go | 46 ++++++++------ .../mcp/tools/tool_k8s_list_pod_containers.go | 6 +- .../tool_k8s_list_pod_containers_test.go | 61 +++++++++++-------- ...ool_k8s_list_top_cpu_consumed_container.go | 6 +- ...8s_list_top_cpu_consumed_container_test.go | 34 ++++++----- ...tool_k8s_list_top_cpu_consumed_workload.go | 6 +- ...k8s_list_top_cpu_consumed_workload_test.go | 43 +++++++------ .../tool_k8s_list_top_http_errors_in_pods.go | 6 +- ...l_k8s_list_top_http_errors_in_pods_test.go | 40 ++++++------ ..._k8s_list_top_memory_consumed_container.go | 6 +- ...list_top_memory_consumed_container_test.go | 37 ++++++----- ...l_k8s_list_top_memory_consumed_workload.go | 6 +- ..._list_top_memory_consumed_workload_test.go | 43 +++++++------ ...ool_k8s_list_top_network_errors_in_pods.go | 6 +- ...8s_list_top_network_errors_in_pods_test.go | 37 ++++++----- .../tools/tool_k8s_list_top_restarted_pods.go | 6 +- .../tool_k8s_list_top_restarted_pods_test.go | 46 ++++++++------ .../tool_k8s_list_top_unavailable_pods.go | 6 +- ...tool_k8s_list_top_unavailable_pods_test.go | 40 ++++++------ ...l_k8s_list_underutilized_pods_cpu_quota.go | 6 +- ..._list_underutilized_pods_cpu_quota_test.go | 43 +++++++------ ...8s_list_underutilized_pods_memory_quota.go | 6 +- ...st_underutilized_pods_memory_quota_test.go | 34 ++++++----- .../mcp/tools/tool_k8s_list_workloads.go | 12 ++-- .../mcp/tools/tool_k8s_list_workloads_test.go | 55 ++++++++++------- .../mcp/tools/tool_list_runtime_events.go | 15 +++-- internal/infra/mcp/tools/tool_run_sysql.go | 6 +- package.nix | 4 +- 43 files changed, 547 insertions(+), 383 deletions(-) diff --git a/docker-base-aarch64.nix b/docker-base-aarch64.nix index 923c6db..afbf1a6 100644 --- a/docker-base-aarch64.nix +++ b/docker-base-aarch64.nix @@ -1,7 +1,7 @@ { imageName = "quay.io/sysdig/sysdig-mini-ubi9"; - imageDigest = "sha256:5e58dfac68d9c9b35e01fcdbb588a1b9b91e4c495e6d503fd6256c56a30f6ed6"; - hash = "sha256-u7wCRKYxK/ycZcR21zWjkPRZ3p8kRl5Hl+tLTsuv394="; + imageDigest = "sha256:e41fa798f88f07e065720f62186b65552ac70d1f33ba36e45c063980950b7bef"; + hash = "sha256-tbngNKTGLym3rENyVKmfYl0PUGAZERyoS78xb3W7AlM="; finalImageName = "quay.io/sysdig/sysdig-mini-ubi9"; finalImageTag = "1"; } diff --git a/docker-base-amd64.nix b/docker-base-amd64.nix index 174517b..b2a2d62 100644 --- a/docker-base-amd64.nix +++ b/docker-base-amd64.nix @@ -1,7 +1,7 @@ { imageName = "quay.io/sysdig/sysdig-mini-ubi9"; - imageDigest = "sha256:5e58dfac68d9c9b35e01fcdbb588a1b9b91e4c495e6d503fd6256c56a30f6ed6"; - hash = "sha256-L4os/2dU0DoC9rXssTJ/q/InmRQ5j3uuUEfinqMhMEs="; + imageDigest = "sha256:e41fa798f88f07e065720f62186b65552ac70d1f33ba36e45c063980950b7bef"; + hash = "sha256-T/imK3ZAQXorg1KtCJ2RYseRkI3TmLzQ4U6832ThZCM="; finalImageName = "quay.io/sysdig/sysdig-mini-ubi9"; finalImageTag = "1"; } diff --git a/flake.lock b/flake.lock index 1067415..9eff508 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1776949667, - "narHash": "sha256-GMSVw35Q+294GlrTUKlx087E31z7KurReQ1YHSKp5iw=", + "lastModified": 1778869304, + "narHash": "sha256-30sZNZoA1cqF5JNO9fVX+wgiQYjB7HJqqJ4ztCDeBZE=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "01fbdeef22b76df85ea168fbfe1bfd9e63681b30", + "rev": "d233902339c02a9c334e7e593de68855ad26c4cb", "type": "github" }, "original": { diff --git a/go.mod b/go.mod index 8d56a43..8fd499f 100644 --- a/go.mod +++ b/go.mod @@ -3,34 +3,34 @@ module github.com/sysdiglabs/sysdig-mcp-server go 1.26 require ( - github.com/mark3labs/mcp-go v0.49.0 + github.com/mark3labs/mcp-go v0.54.0 github.com/oapi-codegen/runtime v1.4.0 - github.com/onsi/ginkgo/v2 v2.28.2 - github.com/onsi/gomega v1.39.1 + github.com/onsi/ginkgo/v2 v2.29.0 + github.com/onsi/gomega v1.41.0 github.com/spf13/cobra v1.10.2 go.uber.org/mock v0.6.0 gopkg.in/yaml.v2 v2.4.0 ) require ( - github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/Masterminds/semver/v3 v3.5.0 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 // indirect + github.com/google/jsonschema-go v0.4.3 // indirect + github.com/google/pprof v0.0.0-20260507013755-92041b743c96 // indirect github.com/google/uuid v1.6.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.35.0 // indirect - golang.org/x/net v0.53.0 // indirect + golang.org/x/mod v0.36.0 // indirect + golang.org/x/net v0.54.0 // indirect golang.org/x/sync v0.20.0 // indirect - golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/tools v0.44.0 // indirect - gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + golang.org/x/sys v0.44.0 // indirect + golang.org/x/text v0.37.0 // indirect + golang.org/x/tools v0.45.0 // indirect ) diff --git a/go.sum b/go.sum index cf79c3b..5f7fac2 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= @@ -8,6 +8,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6N github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/gkampitakis/ciinfo v0.3.2 h1:JcuOPk8ZU7nZQjdUhctuhQofk7BGHuIy0c9Ez8BNhXs= @@ -24,10 +26,10 @@ github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/pprof v0.0.0-20260402051712-545e8a4df936 h1:EwtI+Al+DeppwYX2oXJCETMO23COyaKGP6fHVpkpWpg= -github.com/google/pprof v0.0.0-20260402051712-545e8a4df936/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= +github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= +github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/google/pprof v0.0.0-20260507013755-92041b743c96 h1:YDDnaZ9afWajDboPMt9Vikqca/yWAX7KAxVzb4lJU1M= +github.com/google/pprof v0.0.0-20260507013755-92041b743c96/go.mod h1:MxpfABSjhmINe3F1It9d+8exIHFvUqtLIRCdOGNXqiI= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -39,23 +41,25 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mark3labs/mcp-go v0.49.0 h1:7Ssx4d7/T86qnWoJIdye7wEEvUzv39UIbnZb/FqUZMY= -github.com/mark3labs/mcp-go v0.49.0/go.mod h1:BflTAZAzXlrTpiO44gmjMu89n2FO56rJ9m31fp4zd5k= +github.com/mark3labs/mcp-go v0.54.0 h1:PZhQvd+5xrT43cUoiaKn/hDcvLUhcLc1twSEKYPTcTA= +github.com/mark3labs/mcp-go v0.54.0/go.mod h1:+8WclSK1ZUweCP3hvktSji8n8ABG/95QaEkeVE/Uwas= github.com/maruel/natural v1.1.1 h1:Hja7XhhmvEFhcByqDoHz9QZbkWey+COd9xWfCfn1ioo= github.com/maruel/natural v1.1.1/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/oapi-codegen/runtime v1.4.0 h1:KLOSFOp7UzkbS7Cs1ms6NBEKYr0WmH2wZG0KKbd2er4= github.com/oapi-codegen/runtime v1.4.0/go.mod h1:5sw5fxCDmnOzKNYmkVNF8d34kyUeejJEY8HNT2WaPec= -github.com/onsi/ginkgo/v2 v2.28.2 h1:DTrMfpqxiNUyQ3Y0zhn1n3cOO2euFgQPYIpkWwxVFps= -github.com/onsi/ginkgo/v2 v2.28.2/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= -github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= +github.com/onsi/ginkgo/v2 v2.29.0 h1:rfh+ZFjgJhYWRoIqVf3Uwx/W20yLrcrE2h2GmYVRaag= +github.com/onsi/ginkgo/v2 v2.29.0/go.mod h1:+aXOY+vzZ5mu2iI2HpTZUPmM//oQfsNFX6gU9kNcA44= +github.com/onsi/gomega v1.41.0 h1:OwKp4pXNgVxf6sCplzYo794OFNuoL2q2SBMU5NSWOjA= +github.com/onsi/gomega v1.41.0/go.mod h1:M/Uqpu/8qTjtzCLUA2zJHX9Iilrau25x1PdoSRbWh5A= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= @@ -82,23 +86,23 @@ go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= -golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= -golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4= +golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ= +golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w= +golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc= +golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= +golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8= +golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0= google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/infra/mcp/tools/tool_generate_sysql.go b/internal/infra/mcp/tools/tool_generate_sysql.go index fef257a..b21cc7a 100644 --- a/internal/infra/mcp/tools/tool_generate_sysql.go +++ b/internal/infra/mcp/tools/tool_generate_sysql.go @@ -41,9 +41,11 @@ func (h *ToolGenerateSysql) handle(ctx context.Context, request mcp.CallToolRequ } func (h *ToolGenerateSysql) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("generate_sysql", + tool := mcp.NewTool( + "generate_sysql", mcp.WithDescription(`Generates a SysQL query from a natural language question.`), - mcp.WithString("question", + mcp.WithString( + "question", mcp.Description("A natural language question to be translated into a SysQL query."), mcp.Required(), Examples( diff --git a/internal/infra/mcp/tools/tool_get_event_info.go b/internal/infra/mcp/tools/tool_get_event_info.go index 8f6af5b..7b328b0 100644 --- a/internal/infra/mcp/tools/tool_get_event_info.go +++ b/internal/infra/mcp/tools/tool_get_event_info.go @@ -36,9 +36,11 @@ func (h *ToolGetEventInfo) handle(ctx context.Context, request mcp.CallToolReque } func (h *ToolGetEventInfo) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("get_event_info", + tool := mcp.NewTool( + "get_event_info", mcp.WithDescription("Retrieve detailed information for a specific security event by its ID"), - mcp.WithString("event_id", + mcp.WithString( + "event_id", mcp.Description("The unique identifier of the security event."), mcp.Required(), ), diff --git a/internal/infra/mcp/tools/tool_get_event_process_tree.go b/internal/infra/mcp/tools/tool_get_event_process_tree.go index bdd458f..d031b79 100644 --- a/internal/infra/mcp/tools/tool_get_event_process_tree.go +++ b/internal/infra/mcp/tools/tool_get_event_process_tree.go @@ -54,9 +54,11 @@ func (h *ToolGetEventProcessTree) handle(ctx context.Context, request mcp.CallTo } func (h *ToolGetEventProcessTree) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("get_event_process_tree", + tool := mcp.NewTool( + "get_event_process_tree", mcp.WithDescription("Retrieves the process tree for a specific security event.\nNot every event has a process tree, so this may return an empty tree."), - mcp.WithString("event_id", + mcp.WithString( + "event_id", mcp.Description("The unique identifier of the security event."), mcp.Required(), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_clusters.go b/internal/infra/mcp/tools/tool_k8s_list_clusters.go index 7942083..ce916ad 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_clusters.go +++ b/internal/infra/mcp/tools/tool_k8s_list_clusters.go @@ -25,10 +25,12 @@ func NewK8sListClusters(sysdigClient sysdig.ExtendedClientWithResponsesInterface } func (t *K8sListClusters) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_clusters", + tool := mcp.NewTool( + "k8s_list_clusters", mcp.WithDescription("Lists the cluster information for all clusters or just the cluster specified. Optionally pass start/end (RFC3339) to list clusters that existed at any point in the window."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of clusters to return."), mcp.DefaultNumber(10), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_clusters_test.go b/internal/infra/mcp/tools/tool_k8s_list_clusters_test.go index 5acffe1..2567a17 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_clusters_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_clusters_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListClusters Tool", func() { }) When("listing all clusters", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry(nil, + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + nil, "k8s_list_clusters", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -70,7 +72,8 @@ var _ = Describe("KubernetesListClusters Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_clusters", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -83,7 +86,8 @@ var _ = Describe("KubernetesListClusters Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_clusters", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -96,7 +100,8 @@ var _ = Describe("KubernetesListClusters Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_clusters", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -109,7 +114,8 @@ var _ = Describe("KubernetesListClusters Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry("windowed, no filters", + Entry( + "windowed, no filters", "k8s_list_clusters", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -125,7 +131,8 @@ var _ = Describe("KubernetesListClusters Tool", func() { time.Date(2026, time.April, 16, 11, 0, 0, 0, time.UTC), ), 10), ), - Entry("windowed, cluster_name filter", + Entry( + "windowed, cluster_name filter", "k8s_list_clusters", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_count_pods_per_cluster.go b/internal/infra/mcp/tools/tool_k8s_list_count_pods_per_cluster.go index a058682..60418de 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_count_pods_per_cluster.go +++ b/internal/infra/mcp/tools/tool_k8s_list_count_pods_per_cluster.go @@ -26,11 +26,13 @@ func NewK8sListCountPodsPerCluster(sysdigClient sysdig.ExtendedClientWithRespons } func (t *K8sListCountPodsPerCluster) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_count_pods_per_cluster", + tool := mcp.NewTool( + "k8s_list_count_pods_per_cluster", mcp.WithDescription("List the count of running Kubernetes Pods grouped by cluster and namespace. Optionally pass start/end (RFC3339) to count pods averaged over a historical window instead of the current instant snapshot."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of results to return."), mcp.DefaultNumber(20), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_count_pods_per_cluster_test.go b/internal/infra/mcp/tools/tool_k8s_list_count_pods_per_cluster_test.go index 618d678..44ad3d4 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_count_pods_per_cluster_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_count_pods_per_cluster_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListCountPodsPerCluster Tool", func() { }) When("counting pods", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry(nil, + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + nil, "k8s_list_count_pods_per_cluster", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -70,7 +72,8 @@ var _ = Describe("KubernetesListCountPodsPerCluster Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_count_pods_per_cluster", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -83,7 +86,8 @@ var _ = Describe("KubernetesListCountPodsPerCluster Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_count_pods_per_cluster", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -96,7 +100,8 @@ var _ = Describe("KubernetesListCountPodsPerCluster Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_count_pods_per_cluster", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -109,7 +114,8 @@ var _ = Describe("KubernetesListCountPodsPerCluster Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_count_pods_per_cluster", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -122,7 +128,8 @@ var _ = Describe("KubernetesListCountPodsPerCluster Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry("windowed, both start and end", + Entry( + "windowed, both start and end", "k8s_list_count_pods_per_cluster", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_cronjobs.go b/internal/infra/mcp/tools/tool_k8s_list_cronjobs.go index 4e6e626..540e6e1 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_cronjobs.go +++ b/internal/infra/mcp/tools/tool_k8s_list_cronjobs.go @@ -26,12 +26,14 @@ func NewK8sListCronjobs(sysdigClient sysdig.ExtendedClientWithResponsesInterface } func (t *K8sListCronjobs) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_cronjobs", + tool := mcp.NewTool( + "k8s_list_cronjobs", mcp.WithDescription("Retrieves information from the cronjobs in the cluster. Optionally pass start/end (RFC3339) to list cronjobs that existed at any point in the window."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), mcp.WithString("cronjob_name", mcp.Description("The name of the cronjob to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of cronjobs to return."), mcp.DefaultNumber(10), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_cronjobs_test.go b/internal/infra/mcp/tools/tool_k8s_list_cronjobs_test.go index f94445c..875528e 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_cronjobs_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_cronjobs_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListCronjobs Tool", func() { }) When("listing cronjobs", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry(nil, + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + nil, "k8s_list_cronjobs", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -70,7 +72,8 @@ var _ = Describe("KubernetesListCronjobs Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_cronjobs", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -83,7 +86,8 @@ var _ = Describe("KubernetesListCronjobs Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_cronjobs", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -96,7 +100,8 @@ var _ = Describe("KubernetesListCronjobs Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_cronjobs", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -109,7 +114,8 @@ var _ = Describe("KubernetesListCronjobs Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_cronjobs", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -122,7 +128,8 @@ var _ = Describe("KubernetesListCronjobs Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_cronjobs", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -135,7 +142,8 @@ var _ = Describe("KubernetesListCronjobs Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry("windowed, with cluster filter", + Entry( + "windowed, with cluster filter", "k8s_list_cronjobs", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_nodes.go b/internal/infra/mcp/tools/tool_k8s_list_nodes.go index f44e2f3..c970d5f 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_nodes.go +++ b/internal/infra/mcp/tools/tool_k8s_list_nodes.go @@ -26,11 +26,13 @@ func NewK8sListNodes(sysdigClient sysdig.ExtendedClientWithResponsesInterface, c } func (t *K8sListNodes) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_nodes", + tool := mcp.NewTool( + "k8s_list_nodes", mcp.WithDescription("Lists the information from all nodes, all nodes from a cluster or a specific node with some name. Optionally pass start/end (RFC3339) to list nodes that existed at any point in the window."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("node_name", mcp.Description("The name of the node to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of nodes to return."), mcp.DefaultNumber(10), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_nodes_test.go b/internal/infra/mcp/tools/tool_k8s_list_nodes_test.go index a10f7a1..b52ba5d 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_nodes_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_nodes_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListNodes Tool", func() { }) When("listing nodes", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry(nil, + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + nil, "k8s_list_nodes", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -70,7 +72,8 @@ var _ = Describe("KubernetesListNodes Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_nodes", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -83,7 +86,8 @@ var _ = Describe("KubernetesListNodes Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_nodes", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -96,7 +100,8 @@ var _ = Describe("KubernetesListNodes Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_nodes", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -109,7 +114,8 @@ var _ = Describe("KubernetesListNodes Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_nodes", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -122,7 +128,8 @@ var _ = Describe("KubernetesListNodes Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_nodes", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -135,7 +142,8 @@ var _ = Describe("KubernetesListNodes Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry("windowed, cluster filter", + Entry( + "windowed, cluster filter", "k8s_list_nodes", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_pod_containers.go b/internal/infra/mcp/tools/tool_k8s_list_pod_containers.go index 572af57..e387288 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_pod_containers.go +++ b/internal/infra/mcp/tools/tool_k8s_list_pod_containers.go @@ -26,7 +26,8 @@ func NewK8sListPodContainers(sysdigClient sysdig.ExtendedClientWithResponsesInte } func (t *K8sListPodContainers) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_pod_containers", + tool := mcp.NewTool( + "k8s_list_pod_containers", mcp.WithDescription("Retrieves information from a particular pod and container. Optionally pass start/end (RFC3339) to list pod containers that existed at any point in the window."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), @@ -36,7 +37,8 @@ func (t *K8sListPodContainers) RegisterInServer(s *server.MCPServer) { mcp.WithString("container_name", mcp.Description("The name of the container to filter by.")), mcp.WithString("image_pullstring", mcp.Description("The image pullstring to filter by.")), mcp.WithString("node_name", mcp.Description("The name of the node to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of pod containers to return."), mcp.DefaultNumber(10), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_pod_containers_test.go b/internal/infra/mcp/tools/tool_k8s_list_pod_containers_test.go index 6a4b946..179d71a 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_pod_containers_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_pod_containers_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListPodContainers Tool", func() { }) When("listing pod containers", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry(nil, + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + nil, "k8s_list_pod_containers", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -70,7 +72,8 @@ var _ = Describe("KubernetesListPodContainers Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_pod_containers", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -83,7 +86,8 @@ var _ = Describe("KubernetesListPodContainers Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_pod_containers", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -96,7 +100,8 @@ var _ = Describe("KubernetesListPodContainers Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_pod_containers", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -109,7 +114,8 @@ var _ = Describe("KubernetesListPodContainers Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_pod_containers", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -122,7 +128,8 @@ var _ = Describe("KubernetesListPodContainers Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_pod_containers", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -135,7 +142,8 @@ var _ = Describe("KubernetesListPodContainers Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_pod_containers", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -148,7 +156,8 @@ var _ = Describe("KubernetesListPodContainers Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_pod_containers", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -161,7 +170,8 @@ var _ = Describe("KubernetesListPodContainers Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_pod_containers", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -174,7 +184,8 @@ var _ = Describe("KubernetesListPodContainers Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_pod_containers", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -187,7 +198,8 @@ var _ = Describe("KubernetesListPodContainers Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_pod_containers", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -209,7 +221,8 @@ var _ = Describe("KubernetesListPodContainers Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry("windowed, with cluster filter", + Entry( + "windowed, with cluster filter", "k8s_list_pod_containers", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_container.go b/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_container.go index f315176..061b827 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_container.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_container.go @@ -26,13 +26,15 @@ func NewK8sListTopCPUConsumedContainer(sysdigClient sysdig.ExtendedClientWithRes } func (t *K8sListTopCPUConsumedContainer) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_top_cpu_consumed_container", + tool := mcp.NewTool( + "k8s_list_top_cpu_consumed_container", mcp.WithDescription("Identifies the Kubernetes containers consuming the most CPU (in cores). Optionally pass start/end (RFC3339) to query a historical window (averaged over the window) instead of the current instant snapshot."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), mcp.WithString("workload_type", mcp.Description("The type of the workload to filter by.")), mcp.WithString("workload_name", mcp.Description("The name of the workload to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of containers to return."), mcp.DefaultNumber(20), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_container_test.go b/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_container_test.go index f5fcbcd..462e68f 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_container_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_container_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListTopCPUConsumedContainer Tool", func() { }) When("listing top cpu consumed by container", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(ContainSubstring(`"status":"success"`)) - }, - Entry("with no params", context.Background(), "k8s_list_top_cpu_consumed_container", + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(ContainSubstring(`"status":"success"`)) + }, + Entry( + "with no params", context.Background(), "k8s_list_top_cpu_consumed_container", mcp.CallToolRequest{ Params: mcp.CallToolParams{ Name: "k8s_list_top_cpu_consumed_container", @@ -68,7 +70,8 @@ var _ = Describe("KubernetesListTopCPUConsumedContainer Tool", func() { Query: `topk(20, sum by (kube_cluster_name, kube_namespace_name, kube_workload_type, kube_workload_name, container_label_io_kubernetes_container_name)(sysdig_container_cpu_cores_used))`, }, ), - Entry("with all params", context.Background(), "k8s_list_top_cpu_consumed_container", + Entry( + "with all params", context.Background(), "k8s_list_top_cpu_consumed_container", mcp.CallToolRequest{ Params: mcp.CallToolParams{ Name: "k8s_list_top_cpu_consumed_container", @@ -85,7 +88,8 @@ var _ = Describe("KubernetesListTopCPUConsumedContainer Tool", func() { Query: `topk(10, sum by (kube_cluster_name, kube_namespace_name, kube_workload_type, kube_workload_name, container_label_io_kubernetes_container_name)(sysdig_container_cpu_cores_used{kube_cluster_name="test-cluster",kube_namespace_name="test-namespace",kube_workload_type="deployment",kube_workload_name="test-workload"}))`, }, ), - Entry("windowed, both start and end", context.Background(), "k8s_list_top_cpu_consumed_container", + Entry( + "windowed, both start and end", context.Background(), "k8s_list_top_cpu_consumed_container", mcp.CallToolRequest{ Params: mcp.CallToolParams{ Name: "k8s_list_top_cpu_consumed_container", diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_workload.go b/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_workload.go index 8fd0cba..6224659 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_workload.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_workload.go @@ -26,13 +26,15 @@ func NewK8sListTopCPUConsumedWorkload(sysdigClient sysdig.ExtendedClientWithResp } func (t *K8sListTopCPUConsumedWorkload) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_top_cpu_consumed_workload", + tool := mcp.NewTool( + "k8s_list_top_cpu_consumed_workload", mcp.WithDescription("Identifies the Kubernetes workloads (all containers) consuming the most CPU (in cores). Optionally pass start/end (RFC3339) to query a historical window (averaged over the window) instead of the current instant snapshot."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), mcp.WithString("workload_type", mcp.Description("The type of the workload to filter by.")), mcp.WithString("workload_name", mcp.Description("The name of the workload to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of workloads to return."), mcp.DefaultNumber(20), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_workload_test.go b/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_workload_test.go index f2f8f2d..b938367 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_workload_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_cpu_consumed_workload_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListTopCPUConsumedWorkload Tool", func() { }) When("listing top cpu consumed by workload", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry("instant snapshot, no params", + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + "instant snapshot, no params", "k8s_list_top_cpu_consumed_workload", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -69,7 +71,8 @@ var _ = Describe("KubernetesListTopCPUConsumedWorkload Tool", func() { Query: `topk(20, sum by (kube_cluster_name, kube_namespace_name, kube_workload_type, kube_workload_name)(sysdig_container_cpu_cores_used))`, }, ), - Entry("instant snapshot, cluster+namespace filter", + Entry( + "instant snapshot, cluster+namespace filter", "k8s_list_top_cpu_consumed_workload", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -85,7 +88,8 @@ var _ = Describe("KubernetesListTopCPUConsumedWorkload Tool", func() { Query: `topk(10, sum by (kube_cluster_name, kube_namespace_name, kube_workload_type, kube_workload_name)(sysdig_container_cpu_cores_used{kube_cluster_name="prod",kube_namespace_name="default"}))`, }, ), - Entry("instant snapshot, all filters", + Entry( + "instant snapshot, all filters", "k8s_list_top_cpu_consumed_workload", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -103,7 +107,8 @@ var _ = Describe("KubernetesListTopCPUConsumedWorkload Tool", func() { Query: `topk(5, sum by (kube_cluster_name, kube_namespace_name, kube_workload_type, kube_workload_name)(sysdig_container_cpu_cores_used{kube_cluster_name="prod",kube_namespace_name="default",kube_workload_type="deployment",kube_workload_name="api"}))`, }, ), - Entry("windowed, both start and end", + Entry( + "windowed, both start and end", "k8s_list_top_cpu_consumed_workload", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -119,7 +124,8 @@ var _ = Describe("KubernetesListTopCPUConsumedWorkload Tool", func() { time.Date(2026, time.April, 16, 11, 0, 0, 0, time.UTC), ), ), - Entry("windowed, start only defaults end to now", + Entry( + "windowed, start only defaults end to now", "k8s_list_top_cpu_consumed_workload", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -134,7 +140,8 @@ var _ = Describe("KubernetesListTopCPUConsumedWorkload Tool", func() { time.Date(2026, time.April, 16, 12, 0, 0, 0, time.UTC), ), ), - Entry("windowed, with filters", + Entry( + "windowed, with filters", "k8s_list_top_cpu_consumed_workload", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_http_errors_in_pods.go b/internal/infra/mcp/tools/tool_k8s_list_top_http_errors_in_pods.go index b6c2778..8ee5155 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_http_errors_in_pods.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_http_errors_in_pods.go @@ -27,14 +27,16 @@ func NewK8sListTopHttpErrorsInPods(sysdigClient sysdig.ExtendedClientWithRespons } func (t *K8sListTopHttpErrorsInPods) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_top_http_errors_in_pods", + tool := mcp.NewTool( + "k8s_list_top_http_errors_in_pods", mcp.WithDescription("Lists the pods with the highest rate of HTTP 4xx and 5xx errors over a time window, allowing filtering by cluster, namespace, workload type, and workload name. Pass start/end (RFC3339) to specify the window. The legacy 'interval' param is retained for backward compatibility; start/end take precedence when both are provided."), mcp.WithString("interval", mcp.Description("Time interval for the query (e.g. '1h', '30m'). Default is '1h'. Ignored when start/end are provided.")), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), mcp.WithString("workload_type", mcp.Description("The type of the workload to filter by.")), mcp.WithString("workload_name", mcp.Description("The name of the workload to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of pods to return."), mcp.DefaultNumber(20), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_http_errors_in_pods_test.go b/internal/infra/mcp/tools/tool_k8s_list_top_http_errors_in_pods_test.go index f654139..600fad1 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_http_errors_in_pods_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_http_errors_in_pods_test.go @@ -45,21 +45,23 @@ var _ = Describe("KubernetesListTopHttpErrorsInPods Tool", func() { }) When("listing top http errors", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry("default params (legacy path, no interval explicitly set)", + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + "default params (legacy path, no interval explicitly set)", "k8s_list_top_http_errors_in_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -72,7 +74,8 @@ var _ = Describe("KubernetesListTopHttpErrorsInPods Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry("legacy path, explicit interval", + Entry( + "legacy path, explicit interval", "k8s_list_top_http_errors_in_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -90,7 +93,8 @@ var _ = Describe("KubernetesListTopHttpErrorsInPods Tool", func() { Limit: new(sysdig.LimitQuery(5)), }, ), - Entry("legacy path, all filters", + Entry( + "legacy path, all filters", "k8s_list_top_http_errors_in_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -110,7 +114,8 @@ var _ = Describe("KubernetesListTopHttpErrorsInPods Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry("windowed path via start/end", + Entry( + "windowed path via start/end", "k8s_list_top_http_errors_in_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -126,7 +131,8 @@ var _ = Describe("KubernetesListTopHttpErrorsInPods Tool", func() { time.Date(2026, time.April, 16, 11, 0, 0, 0, time.UTC), ), 20), ), - Entry("windowed path takes precedence over interval", + Entry( + "windowed path takes precedence over interval", "k8s_list_top_http_errors_in_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_container.go b/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_container.go index 942c89e..fddbdc9 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_container.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_container.go @@ -26,13 +26,15 @@ func NewK8sListTopMemoryConsumedContainer(sysdigClient sysdig.ExtendedClientWith } func (t *K8sListTopMemoryConsumedContainer) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_top_memory_consumed_container", + tool := mcp.NewTool( + "k8s_list_top_memory_consumed_container", mcp.WithDescription("Lists memory-intensive containers. Optionally pass start/end (RFC3339) to query a historical window (averaged over the window) instead of the current instant snapshot."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), mcp.WithString("workload_type", mcp.Description("The type of the workload to filter by.")), mcp.WithString("workload_name", mcp.Description("The name of the workload to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of containers to return."), mcp.DefaultNumber(20), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_container_test.go b/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_container_test.go index 660a2dd..ecab1c9 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_container_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_container_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListTopMemoryConsumedContainer Tool", func() { }) When("listing top memory consumed by container", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry(nil, + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + nil, "k8s_list_top_memory_consumed_container", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -70,7 +72,8 @@ var _ = Describe("KubernetesListTopMemoryConsumedContainer Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_top_memory_consumed_container", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -87,7 +90,8 @@ var _ = Describe("KubernetesListTopMemoryConsumedContainer Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_top_memory_consumed_container", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -106,7 +110,8 @@ var _ = Describe("KubernetesListTopMemoryConsumedContainer Tool", func() { Limit: new(sysdig.LimitQuery(5)), }, ), - Entry("windowed, both start and end", + Entry( + "windowed, both start and end", "k8s_list_top_memory_consumed_container", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_workload.go b/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_workload.go index 32cc2d7..5a46267 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_workload.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_workload.go @@ -26,13 +26,15 @@ func NewK8sListTopMemoryConsumedWorkload(sysdigClient sysdig.ExtendedClientWithR } func (t *K8sListTopMemoryConsumedWorkload) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_top_memory_consumed_workload", + tool := mcp.NewTool( + "k8s_list_top_memory_consumed_workload", mcp.WithDescription("Lists memory-intensive workloads (all containers). Optionally pass start/end (RFC3339) to query a historical window (averaged over the window) instead of the current instant snapshot."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), mcp.WithString("workload_type", mcp.Description("The type of the workload to filter by.")), mcp.WithString("workload_name", mcp.Description("The name of the workload to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of workloads to return."), mcp.DefaultNumber(20), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_workload_test.go b/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_workload_test.go index e3f8b4e..0edad51 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_workload_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_memory_consumed_workload_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListTopMemoryConsumedWorkload Tool", func() { }) When("listing top memory consumed by workload", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry(nil, + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + nil, "k8s_list_top_memory_consumed_workload", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -70,7 +72,8 @@ var _ = Describe("KubernetesListTopMemoryConsumedWorkload Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_top_memory_consumed_workload", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -83,7 +86,8 @@ var _ = Describe("KubernetesListTopMemoryConsumedWorkload Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_top_memory_consumed_workload", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -96,7 +100,8 @@ var _ = Describe("KubernetesListTopMemoryConsumedWorkload Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_top_memory_consumed_workload", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -109,7 +114,8 @@ var _ = Describe("KubernetesListTopMemoryConsumedWorkload Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_top_memory_consumed_workload", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -128,7 +134,8 @@ var _ = Describe("KubernetesListTopMemoryConsumedWorkload Tool", func() { Limit: new(sysdig.LimitQuery(5)), }, ), - Entry("windowed, both start and end", + Entry( + "windowed, both start and end", "k8s_list_top_memory_consumed_workload", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_network_errors_in_pods.go b/internal/infra/mcp/tools/tool_k8s_list_top_network_errors_in_pods.go index 83290a0..e9f6a00 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_network_errors_in_pods.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_network_errors_in_pods.go @@ -27,14 +27,16 @@ func NewK8sListTopNetworkErrorsInPods(sysdigClient sysdig.ExtendedClientWithResp } func (t *K8sListTopNetworkErrorsInPods) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_top_network_errors_in_pods", + tool := mcp.NewTool( + "k8s_list_top_network_errors_in_pods", mcp.WithDescription("Shows the top network errors by pod over a time window, aggregated by cluster, namespace, workload type, and workload name. The result is an average rate of network errors per second. Pass start/end (RFC3339) to specify the window. The legacy 'interval' param is retained for backward compatibility; start/end take precedence when both are provided."), mcp.WithString("interval", mcp.Description("Time interval for the query (e.g. '1h', '30m'). Default is '1h'. Ignored when start/end are provided.")), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), mcp.WithString("workload_type", mcp.Description("The type of the workload to filter by.")), mcp.WithString("workload_name", mcp.Description("The name of the workload to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of pods to return."), mcp.DefaultNumber(20), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_network_errors_in_pods_test.go b/internal/infra/mcp/tools/tool_k8s_list_top_network_errors_in_pods_test.go index b2bc677..2be549a 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_network_errors_in_pods_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_network_errors_in_pods_test.go @@ -45,21 +45,23 @@ var _ = Describe("KubernetesListTopNetworkErrorsInPods Tool", func() { }) When("listing top network errors", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry("default params (legacy path)", + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + "default params (legacy path)", "k8s_list_top_network_errors_in_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -72,7 +74,8 @@ var _ = Describe("KubernetesListTopNetworkErrorsInPods Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry("legacy path, explicit interval", + Entry( + "legacy path, explicit interval", "k8s_list_top_network_errors_in_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -90,7 +93,8 @@ var _ = Describe("KubernetesListTopNetworkErrorsInPods Tool", func() { Limit: new(sysdig.LimitQuery(5)), }, ), - Entry("legacy path, all filters", + Entry( + "legacy path, all filters", "k8s_list_top_network_errors_in_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -110,7 +114,8 @@ var _ = Describe("KubernetesListTopNetworkErrorsInPods Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry("windowed path via start/end", + Entry( + "windowed path via start/end", "k8s_list_top_network_errors_in_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_restarted_pods.go b/internal/infra/mcp/tools/tool_k8s_list_top_restarted_pods.go index 24c8f60..c885f5d 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_restarted_pods.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_restarted_pods.go @@ -26,14 +26,16 @@ func NewK8sListTopRestartedPods(sysdigClient sysdig.ExtendedClientWithResponsesI } func (t *K8sListTopRestartedPods) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_top_restarted_pods", + tool := mcp.NewTool( + "k8s_list_top_restarted_pods", mcp.WithDescription("Lists the pods with the highest number of container restarts in the specified scope (cluster, namespace, workload, or individual pod). By default, it returns the top 10. Optionally pass start/end (RFC3339) to count restarts that occurred *within* the window (via increase() on the counter) instead of total lifetime restarts."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), mcp.WithString("workload_type", mcp.Description("The type of the workload to filter by.")), mcp.WithString("workload_name", mcp.Description("The name of the workload to filter by.")), mcp.WithString("pod_name", mcp.Description("The name of the pod to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of pods to return."), mcp.DefaultNumber(10), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_restarted_pods_test.go b/internal/infra/mcp/tools/tool_k8s_list_top_restarted_pods_test.go index 2908cdc..db6be6f 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_restarted_pods_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_restarted_pods_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListTopRestartedPods Tool", func() { }) When("listing top restarted pods", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry(nil, + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + nil, "k8s_list_top_restarted_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -69,7 +71,8 @@ var _ = Describe("KubernetesListTopRestartedPods Tool", func() { Query: `topk(10, sum by(pod, kube_cluster_name, kube_namespace_name) (kube_pod_container_status_restarts_total) > 0)`, }, ), - Entry(nil, + Entry( + nil, "k8s_list_top_restarted_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -81,7 +84,8 @@ var _ = Describe("KubernetesListTopRestartedPods Tool", func() { Query: `topk(20, sum by(pod, kube_cluster_name, kube_namespace_name) (kube_pod_container_status_restarts_total) > 0)`, }, ), - Entry(nil, + Entry( + nil, "k8s_list_top_restarted_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -93,7 +97,8 @@ var _ = Describe("KubernetesListTopRestartedPods Tool", func() { Query: `topk(10, sum by(pod, kube_cluster_name, kube_namespace_name) (kube_pod_container_status_restarts_total{kube_cluster_name="my_cluster"}) > 0)`, }, ), - Entry(nil, + Entry( + nil, "k8s_list_top_restarted_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -105,7 +110,8 @@ var _ = Describe("KubernetesListTopRestartedPods Tool", func() { Query: `topk(10, sum by(pod, kube_cluster_name, kube_namespace_name) (kube_pod_container_status_restarts_total{kube_namespace_name="my_namespace"}) > 0)`, }, ), - Entry(nil, + Entry( + nil, "k8s_list_top_restarted_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -123,7 +129,8 @@ var _ = Describe("KubernetesListTopRestartedPods Tool", func() { Query: `topk(10, sum by(pod, kube_cluster_name, kube_namespace_name) (kube_pod_container_status_restarts_total{kube_cluster_name="my_cluster",kube_namespace_name="my_namespace",kube_workload_type="deployment",kube_workload_name="my_workload",kube_pod_name="my_pod"}) > 0)`, }, ), - Entry("windowed, no filters", + Entry( + "windowed, no filters", "k8s_list_top_restarted_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -139,7 +146,8 @@ var _ = Describe("KubernetesListTopRestartedPods Tool", func() { time.Date(2026, time.April, 16, 11, 0, 0, 0, time.UTC), ), ), - Entry("windowed, with filters", + Entry( + "windowed, with filters", "k8s_list_top_restarted_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_unavailable_pods.go b/internal/infra/mcp/tools/tool_k8s_list_top_unavailable_pods.go index 64f677b..4ecd5a0 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_unavailable_pods.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_unavailable_pods.go @@ -26,13 +26,15 @@ func NewK8sListTopUnavailablePods(sysdigClient sysdig.ExtendedClientWithResponse } func (t *K8sListTopUnavailablePods) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_top_unavailable_pods", + tool := mcp.NewTool( + "k8s_list_top_unavailable_pods", mcp.WithDescription("Shows the top N pods with the highest number of unavailable or unready replicas in a Kubernetes cluster, ordered from highest to lowest. Optionally pass start/end (RFC3339) to report workloads that were *continuously* unavailable for the entire window (matches Sysdig's `WorkloadReplicasMismatch` advisory semantics)."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), mcp.WithString("workload_type", mcp.Description("The type of the workload to filter by.")), mcp.WithString("workload_name", mcp.Description("The name of the workload to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of pods to return."), mcp.DefaultNumber(20), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_top_unavailable_pods_test.go b/internal/infra/mcp/tools/tool_k8s_list_top_unavailable_pods_test.go index cb276d2..bcb20d9 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_top_unavailable_pods_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_top_unavailable_pods_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListTopUnavailablePods Tool", func() { }) When("querying top unavailable pods", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry("default params", + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + "default params", "k8s_list_top_unavailable_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -88,7 +90,8 @@ var _ = Describe("KubernetesListTopUnavailablePods Tool", func() { )`, }, ), - Entry("with specific limit and cluster", + Entry( + "with specific limit and cluster", "k8s_list_top_unavailable_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -122,7 +125,8 @@ var _ = Describe("KubernetesListTopUnavailablePods Tool", func() { )`, }, ), - Entry("with all filters", + Entry( + "with all filters", "k8s_list_top_unavailable_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -159,7 +163,8 @@ var _ = Describe("KubernetesListTopUnavailablePods Tool", func() { )`, }, ), - Entry("windowed, no filters (Sysdig-canonical pattern)", + Entry( + "windowed, no filters (Sysdig-canonical pattern)", "k8s_list_top_unavailable_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -175,7 +180,8 @@ var _ = Describe("KubernetesListTopUnavailablePods Tool", func() { time.Date(2026, time.April, 16, 11, 0, 0, 0, time.UTC), ), ), - Entry("windowed, with cluster filter", + Entry( + "windowed, with cluster filter", "k8s_list_top_unavailable_pods", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_cpu_quota.go b/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_cpu_quota.go index d900afe..9ceed0b 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_cpu_quota.go +++ b/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_cpu_quota.go @@ -26,11 +26,13 @@ func NewK8sListUnderutilizedPodsCPUQuota(sysdigClient sysdig.ExtendedClientWithR } func (t *K8sListUnderutilizedPodsCPUQuota) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_underutilized_pods_cpu_quota", + tool := mcp.NewTool( + "k8s_list_underutilized_pods_cpu_quota", mcp.WithDescription("List Kubernetes pods with CPU usage below 25% of the quota limit. Optionally pass start/end (RFC3339) to evaluate the ratio averaged over a historical window instead of the current instant snapshot."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of pods to return."), mcp.DefaultNumber(10), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_cpu_quota_test.go b/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_cpu_quota_test.go index 2184808..c4f0e11 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_cpu_quota_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_cpu_quota_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListUnderutilizedPodsCPUQuota Tool", func() { }) When("listing underutilized pods", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry(nil, + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + nil, "k8s_list_underutilized_pods_cpu_quota", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -70,7 +72,8 @@ var _ = Describe("KubernetesListUnderutilizedPodsCPUQuota Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_underutilized_pods_cpu_quota", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -83,7 +86,8 @@ var _ = Describe("KubernetesListUnderutilizedPodsCPUQuota Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_underutilized_pods_cpu_quota", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -96,7 +100,8 @@ var _ = Describe("KubernetesListUnderutilizedPodsCPUQuota Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_underutilized_pods_cpu_quota", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -109,7 +114,8 @@ var _ = Describe("KubernetesListUnderutilizedPodsCPUQuota Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_underutilized_pods_cpu_quota", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -122,7 +128,8 @@ var _ = Describe("KubernetesListUnderutilizedPodsCPUQuota Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry("windowed, both start and end", + Entry( + "windowed, both start and end", "k8s_list_underutilized_pods_cpu_quota", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_memory_quota.go b/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_memory_quota.go index f13bcb1..6dc88a5 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_memory_quota.go +++ b/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_memory_quota.go @@ -26,11 +26,13 @@ func NewK8sListUnderutilizedPodsMemoryQuota(sysdigClient sysdig.ExtendedClientWi } func (t *K8sListUnderutilizedPodsMemoryQuota) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_underutilized_pods_memory_quota", + tool := mcp.NewTool( + "k8s_list_underutilized_pods_memory_quota", mcp.WithDescription("List Kubernetes pods with memory usage below 25% of the limit. Optionally pass start/end (RFC3339) to evaluate the ratio averaged over a historical window instead of the current instant snapshot."), mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of pods to return."), mcp.DefaultNumber(10), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_memory_quota_test.go b/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_memory_quota_test.go index 0a93bc2..ae94f40 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_memory_quota_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_underutilized_pods_memory_quota_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListUnderutilizedPodsMemoryQuota Tool", func() { }) When("listing underutilized pods", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry(nil, + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + nil, "k8s_list_underutilized_pods_memory_quota", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -70,7 +72,8 @@ var _ = Describe("KubernetesListUnderutilizedPodsMemoryQuota Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_underutilized_pods_memory_quota", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -87,7 +90,8 @@ var _ = Describe("KubernetesListUnderutilizedPodsMemoryQuota Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry("windowed, both start and end", + Entry( + "windowed, both start and end", "k8s_list_underutilized_pods_memory_quota", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_k8s_list_workloads.go b/internal/infra/mcp/tools/tool_k8s_list_workloads.go index 53ba894..4c7bbf2 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_workloads.go +++ b/internal/infra/mcp/tools/tool_k8s_list_workloads.go @@ -26,9 +26,11 @@ func NewK8sListWorkloads(sysdigClient sysdig.ExtendedClientWithResponsesInterfac } func (t *K8sListWorkloads) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("k8s_list_workloads", + tool := mcp.NewTool( + "k8s_list_workloads", mcp.WithDescription("Lists all the workloads that are in a particular state, desired, ready, running or unavailable. The LLM can filter by cluster, namespace, workload name or type. Optionally pass start/end (RFC3339) to query over a historical window (peak value per workload; for the 'unavailable' status only workloads unavailable at any point in the window are returned)."), - mcp.WithString("status", + mcp.WithString( + "status", mcp.Description("The status of the workload."), mcp.Enum("desired", "ready", "running", "unavailable"), mcp.Required(), @@ -36,11 +38,13 @@ func (t *K8sListWorkloads) RegisterInServer(s *server.MCPServer) { mcp.WithString("cluster_name", mcp.Description("The name of the cluster to filter by.")), mcp.WithString("namespace_name", mcp.Description("The name of the namespace to filter by.")), mcp.WithString("workload_name", mcp.Description("The name of the workload to filter by.")), - mcp.WithString("workload_type", + mcp.WithString( + "workload_type", mcp.Description("The type of the workload."), mcp.Enum("deployment", "daemonset", "statefulset"), ), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of workloads to return."), mcp.DefaultNumber(10), ), diff --git a/internal/infra/mcp/tools/tool_k8s_list_workloads_test.go b/internal/infra/mcp/tools/tool_k8s_list_workloads_test.go index c1dd0ee..7c067c1 100644 --- a/internal/infra/mcp/tools/tool_k8s_list_workloads_test.go +++ b/internal/infra/mcp/tools/tool_k8s_list_workloads_test.go @@ -43,21 +43,23 @@ var _ = Describe("KubernetesListWorkloads Tool", func() { }) When("listing workloads", func() { - DescribeTable("it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { - mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ - StatusCode: http.StatusOK, - Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), - }, nil) + DescribeTable( + "it succeeds", func(ctx context.Context, toolName string, request mcp.CallToolRequest, expectedParamsRequested sysdig.GetQueryV1Params) { + mockSysdig.EXPECT().GetQueryV1(gomock.Any(), &expectedParamsRequested).Return(&http.Response{ + StatusCode: http.StatusOK, + Body: io.NopCloser(bytes.NewBufferString(`{"status":"success"}`)), + }, nil) - serverTool := mcpServer.GetTool(toolName) - result, err := serverTool.Handler(ctx, request) - Expect(err).NotTo(HaveOccurred()) + serverTool := mcpServer.GetTool(toolName) + result, err := serverTool.Handler(ctx, request) + Expect(err).NotTo(HaveOccurred()) - resultData, ok := result.Content[0].(mcp.TextContent) - Expect(ok).To(BeTrue()) - Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) - }, - Entry(nil, + resultData, ok := result.Content[0].(mcp.TextContent) + Expect(ok).To(BeTrue()) + Expect(resultData.Text).To(MatchJSON(`{"status":"success"}`)) + }, + Entry( + nil, "k8s_list_workloads", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -70,7 +72,8 @@ var _ = Describe("KubernetesListWorkloads Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_workloads", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -83,7 +86,8 @@ var _ = Describe("KubernetesListWorkloads Tool", func() { Limit: new(sysdig.LimitQuery(20)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_workloads", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -96,7 +100,8 @@ var _ = Describe("KubernetesListWorkloads Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_workloads", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -109,7 +114,8 @@ var _ = Describe("KubernetesListWorkloads Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_workloads", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -122,7 +128,8 @@ var _ = Describe("KubernetesListWorkloads Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_workloads", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -135,7 +142,8 @@ var _ = Describe("KubernetesListWorkloads Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry(nil, + Entry( + nil, "k8s_list_workloads", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -154,7 +162,8 @@ var _ = Describe("KubernetesListWorkloads Tool", func() { Limit: new(sysdig.LimitQuery(10)), }, ), - Entry("windowed, ready status (no > 0 guard)", + Entry( + "windowed, ready status (no > 0 guard)", "k8s_list_workloads", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -171,7 +180,8 @@ var _ = Describe("KubernetesListWorkloads Tool", func() { time.Date(2026, time.April, 16, 11, 0, 0, 0, time.UTC), ), 10), ), - Entry("windowed, desired status (no > 0 guard)", + Entry( + "windowed, desired status (no > 0 guard)", "k8s_list_workloads", mcp.CallToolRequest{ Params: mcp.CallToolParams{ @@ -189,7 +199,8 @@ var _ = Describe("KubernetesListWorkloads Tool", func() { time.Date(2026, time.April, 16, 11, 0, 0, 0, time.UTC), ), 10), ), - Entry("windowed, unavailable status (> 0 guard)", + Entry( + "windowed, unavailable status (> 0 guard)", "k8s_list_workloads", mcp.CallToolRequest{ Params: mcp.CallToolParams{ diff --git a/internal/infra/mcp/tools/tool_list_runtime_events.go b/internal/infra/mcp/tools/tool_list_runtime_events.go index 9f1eb3b..d3782da 100644 --- a/internal/infra/mcp/tools/tool_list_runtime_events.go +++ b/internal/infra/mcp/tools/tool_list_runtime_events.go @@ -62,20 +62,25 @@ func toolRequestToEventsV1Params(request mcp.CallToolRequest, clock clock.Clock) } func (h *ToolListRuntimeEvents) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("list_runtime_events", + tool := mcp.NewTool( + "list_runtime_events", mcp.WithDescription("List runtime security events from the last given hours, optionally filtered by severity level. Includes both Falco-based and machine learning (ML) detections such as crypto mining, anomalous logins, and other ML-detected threats."), - mcp.WithString("cursor", + mcp.WithString( + "cursor", mcp.Description("Cursor for pagination."), ), - mcp.WithNumber("scope_hours", + mcp.WithNumber( + "scope_hours", mcp.Description("Number of hours back from now to include events."), mcp.DefaultNumber(1), ), - mcp.WithNumber("limit", + mcp.WithNumber( + "limit", mcp.Description("Maximum number of events to return. Maximum allowed value is 200."), mcp.DefaultNumber(50), ), - mcp.WithString("filter_expr", + mcp.WithString( + "filter_expr", mcp.Description(`Logical filter expression to select runtime security events. Supports operators: =, !=, in, contains, startsWith, exists. Combine with and/or/not. diff --git a/internal/infra/mcp/tools/tool_run_sysql.go b/internal/infra/mcp/tools/tool_run_sysql.go index 38e96b0..aa9fb24 100644 --- a/internal/infra/mcp/tools/tool_run_sysql.go +++ b/internal/infra/mcp/tools/tool_run_sysql.go @@ -47,9 +47,11 @@ func (h *ToolRunSysql) handle(ctx context.Context, request mcp.CallToolRequest) } func (h *ToolRunSysql) RegisterInServer(s *server.MCPServer) { - tool := mcp.NewTool("run_sysql", + tool := mcp.NewTool( + "run_sysql", mcp.WithDescription(`Execute a SysQL query directly against the Sysdig API. You should try generating a SysQL query first to ensure that it's valid.`), - mcp.WithString("sysql_query", + mcp.WithString( + "sysql_query", mcp.Description("A valid SysQL query string to execute directly."), mcp.Required(), Examples( diff --git a/package.nix b/package.nix index 79ef793..ce5b687 100644 --- a/package.nix +++ b/package.nix @@ -1,10 +1,10 @@ { buildGo126Module, versionCheckHook }: buildGo126Module (finalAttrs: { pname = "sysdig-mcp-server"; - version = "1.0.8"; + version = "1.0.9"; src = ./.; # This hash is automatically re-calculated with `just rehash-package-nix`. This is automatically called as well by `just update`. - vendorHash = "sha256-OtXl71IUEq+n+tL9q79t2qq68uwj4a4MLJBGCvZwy0o="; + vendorHash = "sha256-/+0Vi3jhlx7wsQWct4kgfDho2BJRqSqXz8LnzvY7BcI="; subPackages = [ "cmd/server"