From 56e1114546df26d10bf384827df2ee4676704bf6 Mon Sep 17 00:00:00 2001 From: Ivan Necas Date: Mon, 2 Mar 2026 15:16:59 +0100 Subject: [PATCH 1/4] GIE-497: olsUi basic implementation --- pkg/tools/definitions.go | 5 +++++ pkg/tools/tooldef.go | 23 +++++++++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/pkg/tools/definitions.go b/pkg/tools/definitions.go index 6ae5534..1db5508 100644 --- a/pkg/tools/definitions.go +++ b/pkg/tools/definitions.go @@ -86,6 +86,11 @@ var ( Pattern: `^\d+[smhdwy]$`, }, }, + AdditionalFields: map[string]any{ + "olsUi": map[string]any{ + "id": "mcp-obs/execute-range-query", + }, + }, } GetLabelNames = ToolDef{ diff --git a/pkg/tools/tooldef.go b/pkg/tools/tooldef.go index 113dcd7..bf643f9 100644 --- a/pkg/tools/tooldef.go +++ b/pkg/tools/tooldef.go @@ -27,14 +27,15 @@ const ( // ToolDef defines a tool that can be converted to different formats (MCP, Toolset, etc.) type ToolDef struct { - Name string - Description string - Title string - Params []ParamDef - ReadOnly bool - Destructive bool - Idempotent bool - OpenWorld bool + Name string + Description string + Title string + Params []ParamDef + ReadOnly bool + Destructive bool + Idempotent bool + OpenWorld bool + AdditionalFields map[string]any } // ToMCPTool converts a ToolDef to an mcp.Tool @@ -64,6 +65,12 @@ func (d ToolDef) ToMCPTool() mcp.Tool { tool := mcp.NewTool(d.Name, opts...) + if d.AdditionalFields != nil { + tool.Meta = &mcp.Meta{ + AdditionalFields: d.AdditionalFields, + } + } + // Workaround for tools with no parameters // See https://github.com/containers/kubernetes-mcp-server/pull/341/files if len(d.Params) == 0 { From f0a65268f28f396f14161316f8350972a1de5c48 Mon Sep 17 00:00:00 2001 From: Ivan Necas Date: Mon, 2 Mar 2026 15:31:00 +0100 Subject: [PATCH 2/4] GIE-497: Update kubernetes-mcp-server to v0.0.58 To support tool.meta attribute. --- go.mod | 28 ++++++++++++------------ go.sum | 68 +++++++++++++++++++++++++++++----------------------------- 2 files changed, 48 insertions(+), 48 deletions(-) diff --git a/go.mod b/go.mod index f59713a..a138e43 100644 --- a/go.mod +++ b/go.mod @@ -1,22 +1,22 @@ module github.com/rhobs/obs-mcp -go 1.25.0 +go 1.25.6 require ( github.com/BurntSushi/toml v1.6.0 github.com/blang/semver/v4 v4.0.0 - github.com/containers/kubernetes-mcp-server v0.0.57 + github.com/containers/kubernetes-mcp-server v0.0.58 github.com/go-openapi/runtime v0.29.2 github.com/go-openapi/strfmt v0.25.0 github.com/google/jsonschema-go v0.4.2 github.com/mark3labs/mcp-go v0.43.2 github.com/prometheus/alertmanager v0.30.1 github.com/prometheus/client_golang v1.23.2 - github.com/prometheus/common v0.67.4 + github.com/prometheus/common v0.67.5 github.com/prometheus/prometheus v0.307.3 - k8s.io/api v0.35.0 - k8s.io/apimachinery v0.35.0 - k8s.io/client-go v0.35.0 + k8s.io/api v0.35.1 + k8s.io/apimachinery v0.35.1 + k8s.io/client-go v0.35.1 k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 ) @@ -87,27 +87,27 @@ require ( github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.mongodb.org/mongo-driver v1.17.6 // indirect go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/otel v1.39.0 // indirect - go.opentelemetry.io/otel/metric v1.39.0 // indirect - go.opentelemetry.io/otel/trace v1.39.0 // indirect + go.opentelemetry.io/otel v1.40.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/trace v1.40.0 // indirect go.uber.org/atomic v1.11.0 // indirect go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/net v0.48.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.35.0 // indirect golang.org/x/sync v0.19.0 // indirect golang.org/x/sys v0.40.0 // indirect golang.org/x/term v0.39.0 // indirect golang.org/x/text v0.33.0 // indirect golang.org/x/time v0.13.0 // indirect - google.golang.org/protobuf v1.36.10 // indirect + google.golang.org/protobuf v1.36.11 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/cli-runtime v0.35.0 // indirect + k8s.io/cli-runtime v0.35.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect - k8s.io/metrics v0.35.0 // indirect + k8s.io/metrics v0.35.1 // indirect sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/kustomize/api v0.20.1 // indirect sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect diff --git a/go.sum b/go.sum index 7c0b7ea..83b9413 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/containers/kubernetes-mcp-server v0.0.57 h1:+30MpG+N/Zz/XYLCtnp39jZ5zYuYbor9C3/9U4YW1jw= -github.com/containers/kubernetes-mcp-server v0.0.57/go.mod h1:0hqFye3ZFRN3Y9Z6PdRe85VHrwdpKVhE7yEeovSU8gQ= +github.com/containers/kubernetes-mcp-server v0.0.58 h1:OuyNoZDi30zNCzk9M88AOngPygcwVfTiS3nm4Ekn0uM= +github.com/containers/kubernetes-mcp-server v0.0.58/go.mod h1:YoUjn9boECTahbPlGJ3V3//uj00qxvRcymN8AuahsB4= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -230,8 +230,8 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.67.4 h1:yR3NqWO1/UyO1w2PhUvXlGQs/PtFmoveVO0KZ4+Lvsc= -github.com/prometheus/common v0.67.4/go.mod h1:gP0fq6YjjNCLssJCQp0yk4M8W6ikLURwkdd/YKtTbyI= +github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4= +github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= @@ -273,14 +273,14 @@ go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= -go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48= -go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8= -go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0= -go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs= -go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18= -go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE= -go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= -go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -289,16 +289,16 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= 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/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= -golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= golang.org/x/exp v0.0.0-20250808145144-a408d31f581a h1:Y+7uR/b1Mw2iSXZ3G//1haIiSElDQZ8KWh0h+sZPG90= golang.org/x/exp v0.0.0-20250808145144-a408d31f581a/go.mod h1:rT6SFzZ7oxADUDx58pcaKFTcZ+inxAa9fTrYx/uVYwg= golang.org/x/mod v0.31.0 h1:HaW9xtz0+kOcWKwli0ZXy79Ix+UW/vOfmWI5QVd2tgI= golang.org/x/mod v0.31.0/go.mod h1:43JraMp9cGx1Rx3AqioxrbrhNsLl2l/iNAvuBkrezpg= -golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= -golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ= +golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -314,12 +314,12 @@ golang.org/x/tools v0.40.0 h1:yLkxfA+Qnul4cs9QA3KnlFu0lVmd8JJfoq+E41uSutA= golang.org/x/tools v0.40.0/go.mod h1:Ik/tzLRlbscWpqqMRjyWYDisX8bG13FrdXp3o4Sr9lc= google.golang.org/api v0.250.0 h1:qvkwrf/raASj82UegU2RSDGWi/89WkLckn4LuO4lVXM= google.golang.org/api v0.250.0/go.mod h1:Y9Uup8bDLJJtMzJyQnu+rLRJLA0wn+wTtc6vTlOvfXo= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww= -google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= -google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= -google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= -google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= -google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409 h1:H86B94AW+VfJWDqFeEbBPhEtHzJwJfTbgE2lZa54ZAQ= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260128011058-8636f8732409/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= +google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc= +google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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= @@ -330,20 +330,20 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.35.0 h1:iBAU5LTyBI9vw3L5glmat1njFK34srdLmktWwLTprlY= -k8s.io/api v0.35.0/go.mod h1:AQ0SNTzm4ZAczM03QH42c7l3bih1TbAXYo0DkF8ktnA= -k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8= -k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/cli-runtime v0.35.0 h1:PEJtYS/Zr4p20PfZSLCbY6YvaoLrfByd6THQzPworUE= -k8s.io/cli-runtime v0.35.0/go.mod h1:VBRvHzosVAoVdP3XwUQn1Oqkvaa8facnokNkD7jOTMY= -k8s.io/client-go v0.35.0 h1:IAW0ifFbfQQwQmga0UdoH0yvdqrbwMdq9vIFEhRpxBE= -k8s.io/client-go v0.35.0/go.mod h1:q2E5AAyqcbeLGPdoRB+Nxe3KYTfPce1Dnu1myQdqz9o= +k8s.io/api v0.35.1 h1:0PO/1FhlK/EQNVK5+txc4FuhQibV25VLSdLMmGpDE/Q= +k8s.io/api v0.35.1/go.mod h1:28uR9xlXWml9eT0uaGo6y71xK86JBELShLy4wR1XtxM= +k8s.io/apimachinery v0.35.1 h1:yxO6gV555P1YV0SANtnTjXYfiivaTPvCTKX6w6qdDsU= +k8s.io/apimachinery v0.35.1/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/cli-runtime v0.35.1 h1:uKcXFe8J7AMAM4Gm2JDK4mp198dBEq2nyeYtO+JfGJE= +k8s.io/cli-runtime v0.35.1/go.mod h1:55/hiXIq1C8qIJ3WBrWxEwDLdHQYhBNRdZOz9f7yvTw= +k8s.io/client-go v0.35.1 h1:+eSfZHwuo/I19PaSxqumjqZ9l5XiTEKbIaJ+j1wLcLM= +k8s.io/client-go v0.35.1/go.mod h1:1p1KxDt3a0ruRfc/pG4qT/3oHmUj1AhSHEcxNSGg+OA= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= -k8s.io/metrics v0.35.0 h1:xVFoqtAGm2dMNJAcB5TFZJPCen0uEqqNt52wW7ABbX8= -k8s.io/metrics v0.35.0/go.mod h1:g2Up4dcBygZi2kQSEQVDByFs+VUwepJMzzQLJJLpq4M= +k8s.io/metrics v0.35.1 h1:MUcrUcWlq81XiripkydzCGsY9zQawDXfP9IICNNcVVw= +k8s.io/metrics v0.35.1/go.mod h1:9x7xWOAOiWzHA0vaqLgSE4PXF3vyT5ts5XIbx8OSjiI= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck= k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= From 4322aeb0de42c212175aa09e4f7dfa56252d43b4 Mon Sep 17 00:00:00 2001 From: Ivan Necas Date: Mon, 2 Mar 2026 15:33:21 +0100 Subject: [PATCH 3/4] GIE-497: pass meta to a toolset --- pkg/tools/tooldef.go | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/pkg/tools/tooldef.go b/pkg/tools/tooldef.go index bf643f9..ce475f7 100644 --- a/pkg/tools/tooldef.go +++ b/pkg/tools/tooldef.go @@ -117,19 +117,27 @@ func (d ToolDef) ToServerTool(handler func(api.ToolHandlerParams) (*api.ToolCall inputSchema.Required = required } - return api.ServerTool{ - Tool: api.Tool{ - Name: d.Name, - Description: d.Description, - InputSchema: inputSchema, - Annotations: api.ToolAnnotations{ - Title: d.Title, - ReadOnlyHint: ptr.To(d.ReadOnly), - DestructiveHint: ptr.To(d.Destructive), - IdempotentHint: ptr.To(d.Idempotent), - OpenWorldHint: ptr.To(d.OpenWorld), - }, + tool := api.Tool{ + Name: d.Name, + Description: d.Description, + InputSchema: inputSchema, + Annotations: api.ToolAnnotations{ + Title: d.Title, + ReadOnlyHint: ptr.To(d.ReadOnly), + DestructiveHint: ptr.To(d.Destructive), + IdempotentHint: ptr.To(d.Idempotent), + OpenWorldHint: ptr.To(d.OpenWorld), }, + } + + if d.AdditionalFields != nil { + tool.Meta = map[string]any{ + "AdditionalFields": d.AdditionalFields, + } + } + + return api.ServerTool{ + Tool: tool, Handler: handler, // TODO(saswatamcode): Modify this selectively on ACM setups. ClusterAware: ptr.To(false), From 3c05ec2a9adc90c158afb180cc3ca4dc0d93005f Mon Sep 17 00:00:00 2001 From: Ivan Necas Date: Fri, 20 Mar 2026 17:36:20 +0100 Subject: [PATCH 4/4] GIE-497 - add special tool for show_timeseries This addresses two problems: - too many visualizations when multiple execute_range_query tools are called during the response - using instance query - for visuzliation time series is usually still more useful to illustrate the data. --- TOOLS.md | 27 +++++++++++++++++++++++++++ pkg/mcp/handlers.go | 5 +++++ pkg/mcp/handlers_test.go | 33 +++++++++++++++++++++++++++++++++ pkg/mcp/server.go | 3 +++ pkg/mcp/tools.go | 6 ++++++ pkg/tools/definitions.go | 29 ++++++++++++++++++++++++++++- pkg/tools/handlers.go | 22 ++++++++++++++++++++++ pkg/tools/prompt.go | 14 ++++++++++++++ pkg/tools/schema.go | 7 +++++++ pkg/toolset/tools/handlers.go | 10 ++++++++++ pkg/toolset/tools/tools.go | 7 +++++++ pkg/toolset/toolset.go | 1 + 12 files changed, 163 insertions(+), 1 deletion(-) diff --git a/TOOLS.md b/TOOLS.md index c767166..5f00b73 100644 --- a/TOOLS.md +++ b/TOOLS.md @@ -92,6 +92,33 @@ This MCP server exposes the following tools for interacting with Prometheus/Than --- +## `show_timeseries` + +> Display the results as an interactive timeseries chart. + +**Usage Tips:** + +- This tool works like execute_range_query but renders the results as a visual chart in the UI clients. Use it when the user wants to see a graph or visualization of time-series data and to use visuals to provide the answer. Use the show_timeseries as the last tool call after all the other Prometheus tool calls where finalized. +- TIME PARAMETERS: - 'duration': Look back from now (e.g., "5m", "1h", "24h") - 'step': Data point resolution (e.g., "1m" for 1-hour duration, "5m" for 24-hour duration) - 'title': A descriptive chart title (e.g., "API Error Rate Over Last Hour") - 'description': An explanation of the chart's meaning or context (e.g., "Shows the rate of HTTP 5xx errors per second, broken down by pod") +- The 'query' parameter MUST be a range query and must use metric names that were returned by list_metrics. + +**Parameters:** + +| Parameter | Type | Required | Description | +| :------------ | :------- | :------: | :----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `query` | `string` | ✅ | PromQL query string using metric names verified via list_metrics | +| `step` | `string` | ✅ | Query resolution step width (e.g., '15s', '1m', '1h'). Choose based on time range: shorter ranges use smaller steps. | +| `description` | `string` | | Explanation of the chart's meaning or context (e.g., 'Shows the rate of HTTP 5xx errors per second, broken down by pod'). Displayed below the title when provided. | +| `duration` | `string` | | Duration to look back from now (e.g., '1h', '30m', '1d', '2w') (optional) | +| `end` | `string` | | End time as RFC3339 or Unix timestamp (optional). Use `NOW` for current time. | +| `start` | `string` | | Start time as RFC3339 or Unix timestamp (optional) | +| `title` | `string` | | Human-readable chart title describing what the query shows (e.g., 'API Error Rate Over Last Hour'). Displayed above the chart when provided. | + +> [!NOTE] +> Parameters with patterns must match: `^\d+[smhdwy]$` + +--- + ## `get_label_names` > Get all label names (dimensions) available for filtering a metric. diff --git a/pkg/mcp/handlers.go b/pkg/mcp/handlers.go index 6090aee..c1bba02 100644 --- a/pkg/mcp/handlers.go +++ b/pkg/mcp/handlers.go @@ -59,6 +59,11 @@ func ExecuteRangeQueryHandler(opts ObsMCPOptions) func(context.Context, mcp.Call }) } +// ShowTimeseriesHandler handles the show_timeseries tool. +func ShowTimeseriesHandler(opts ObsMCPOptions) func(context.Context, mcp.CallToolRequest) (*mcp.CallToolResult, error) { + return createPrometheusToolHandler(opts, tools.BuildShowTimeseriesInput, tools.ShowTimeseriesHandler) +} + // ExecuteInstantQueryHandler handles the execution of Prometheus instant queries. func ExecuteInstantQueryHandler(opts ObsMCPOptions) func(context.Context, mcp.CallToolRequest) (*mcp.CallToolResult, error) { return createPrometheusToolHandler(opts, tools.BuildInstantQueryInput, tools.ExecuteInstantQueryHandler) diff --git a/pkg/mcp/handlers_test.go b/pkg/mcp/handlers_test.go index 24c40bb..896b795 100644 --- a/pkg/mcp/handlers_test.go +++ b/pkg/mcp/handlers_test.go @@ -573,6 +573,39 @@ func TestExecuteRangeQueryHandler_RelativeTime(t *testing.T) { } } +func TestShowTimeseriesHandler(t *testing.T) { + queryCalled := false + mockClient := &MockedLoader{ + ExecuteRangeQueryFunc: func(ctx context.Context, query string, start, end time.Time, step time.Duration) (map[string]any, error) { + queryCalled = true + if query != "up{job=\"api\"}" { + t.Errorf("expected query 'up{job=\"api\"}', got %q", query) + } + return map[string]any{"resultType": "matrix", "result": []any{}}, nil + }, + } + + ctx := withMockClient(context.Background(), mockClient) + handler := ShowTimeseriesHandler(ObsMCPOptions{}) + req := newMockRequest(map[string]any{ + "query": "up{job=\"api\"}", + "step": "1m", + "start": "2024-01-01T00:00:00Z", + "end": "2024-01-01T01:00:00Z", + }) + + result, err := handler(ctx, req) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if result.IsError { + t.Fatalf("unexpected error result: %v", getErrorMessage(t, result)) + } + if !queryCalled { + t.Fatal("expected range query to be executed") + } +} + func TestExecuteInstantQueryHandler_RelativeTime(t *testing.T) { tests := []struct { name string diff --git a/pkg/mcp/server.go b/pkg/mcp/server.go index c500555..7706b35 100644 --- a/pkg/mcp/server.go +++ b/pkg/mcp/server.go @@ -56,6 +56,7 @@ func SetupTools(mcpServer *server.MCPServer, opts ObsMCPOptions) error { listMetricsTool := CreateListMetricsTool() executeInstantQueryTool := CreateExecuteInstantQueryTool() executeRangeQueryTool := CreateExecuteRangeQueryTool() + showTimeseriesTool := CreateShowTimeseriesTool() getLabelNamesTool := CreateGetLabelNamesTool() getLabelValuesTool := CreateGetLabelValuesTool() getSeriesTool := CreateGetSeriesTool() @@ -66,6 +67,7 @@ func SetupTools(mcpServer *server.MCPServer, opts ObsMCPOptions) error { listMetricsHandler := ListMetricsHandler(opts) executeInstantQueryHandler := ExecuteInstantQueryHandler(opts) executeRangeQueryHandler := ExecuteRangeQueryHandler(opts) + showTimeseriesHandler := ShowTimeseriesHandler(opts) getLabelNamesHandler := GetLabelNamesHandler(opts) getLabelValuesHandler := GetLabelValuesHandler(opts) getSeriesHandler := GetSeriesHandler(opts) @@ -76,6 +78,7 @@ func SetupTools(mcpServer *server.MCPServer, opts ObsMCPOptions) error { mcpServer.AddTool(listMetricsTool, listMetricsHandler) mcpServer.AddTool(executeInstantQueryTool, executeInstantQueryHandler) mcpServer.AddTool(executeRangeQueryTool, executeRangeQueryHandler) + mcpServer.AddTool(showTimeseriesTool, showTimeseriesHandler) mcpServer.AddTool(getLabelNamesTool, getLabelNamesHandler) mcpServer.AddTool(getLabelValuesTool, getLabelValuesHandler) mcpServer.AddTool(getSeriesTool, getSeriesHandler) diff --git a/pkg/mcp/tools.go b/pkg/mcp/tools.go index 046ff0a..b21244b 100644 --- a/pkg/mcp/tools.go +++ b/pkg/mcp/tools.go @@ -13,6 +13,7 @@ func AllTools() []mcp.Tool { CreateListMetricsTool(), CreateExecuteInstantQueryTool(), CreateExecuteRangeQueryTool(), + CreateShowTimeseriesTool(), CreateGetLabelNamesTool(), CreateGetLabelValuesTool(), CreateGetSeriesTool(), @@ -42,6 +43,11 @@ func CreateExecuteRangeQueryTool() mcp.Tool { return tool } +func CreateShowTimeseriesTool() mcp.Tool { + // For UI purposes only, no additional data to be sent to the LLM context. + return tools.ShowTimeseries.ToMCPTool() +} + func CreateGetLabelNamesTool() mcp.Tool { tool := tools.GetLabelNames.ToMCPTool() mcp.WithOutputSchema[tools.LabelNamesOutput]()(&tool) diff --git a/pkg/tools/definitions.go b/pkg/tools/definitions.go index 1db5508..971c5e0 100644 --- a/pkg/tools/definitions.go +++ b/pkg/tools/definitions.go @@ -1,5 +1,7 @@ package tools +import "slices" + // All tool definitions as a single source of truth var ( ListMetrics = ToolDef{ @@ -86,9 +88,33 @@ var ( Pattern: `^\d+[smhdwy]$`, }, }, + } + + ShowTimeseries = ToolDef{ + Name: "show_timeseries", + Description: ShowTimeseriesPrompt, + Title: "Show Timeseries Chart", + ReadOnly: true, + Destructive: false, + Idempotent: true, + OpenWorld: true, + Params: slices.Concat(ExecuteRangeQuery.Params, []ParamDef{ + { + Name: "title", + Type: ParamTypeString, + Description: "Human-readable chart title describing what the query shows (e.g., 'API Error Rate Over Last Hour'). Displayed above the chart when provided.", + Required: false, + }, + { + Name: "description", + Type: ParamTypeString, + Description: "Explanation of the chart's meaning or context (e.g., 'Shows the rate of HTTP 5xx errors per second, broken down by pod'). Displayed below the title when provided.", + Required: false, + }, + }), AdditionalFields: map[string]any{ "olsUi": map[string]any{ - "id": "mcp-obs/execute-range-query", + "id": "mcp-obs/show-timeseries", }, }, } @@ -262,6 +288,7 @@ func AllTools() []ToolDef { ListMetrics, ExecuteInstantQuery, ExecuteRangeQuery, + ShowTimeseries, GetLabelNames, GetLabelValues, GetSeries, diff --git a/pkg/tools/handlers.go b/pkg/tools/handlers.go index d5fbcda..95e9158 100644 --- a/pkg/tools/handlers.go +++ b/pkg/tools/handlers.go @@ -195,6 +195,14 @@ func BuildRangeQueryInput(args map[string]any) RangeQueryInput { } } +func BuildShowTimeseriesInput(args map[string]any) ShowTimeseriesInput { + return ShowTimeseriesInput{ + RangeQueryInput: BuildRangeQueryInput(args), + Title: GetString(args, "title", ""), + Description: GetString(args, "description", ""), + } +} + func BuildLabelNamesInput(args map[string]any) LabelNamesInput { return LabelNamesInput{ Metric: GetString(args, "metric", ""), @@ -368,6 +376,20 @@ func ExecuteRangeQueryHandler(ctx context.Context, promClient prometheus.Loader, return resultutil.NewSuccessResult(output) } +// ShowTimeseriesHandler handles the show_timeseries tool, returning full range query data for chart rendering. +func ShowTimeseriesHandler(ctx context.Context, promClient prometheus.Loader, input ShowTimeseriesInput) *resultutil.Result { + slog.Info("ShowTimeseriesHandler called") + slog.Debug("ShowTimeseriesHandler params", "input", input) + + // Executing the query handler just to validate the query is correct. + result := ExecuteRangeQueryHandler(ctx, promClient, input.RangeQueryInput, true) + if result.Error != nil { + return result + } + + return resultutil.NewSuccessResult(struct{}{}) +} + // ExecuteInstantQueryHandler handles the execution of Prometheus instant queries. func ExecuteInstantQueryHandler(ctx context.Context, promClient prometheus.Loader, input InstantQueryInput) *resultutil.Result { slog.Info("ExecuteInstantQueryHandler called") diff --git a/pkg/tools/prompt.go b/pkg/tools/prompt.go index 7137a94..ef74adb 100644 --- a/pkg/tools/prompt.go +++ b/pkg/tools/prompt.go @@ -85,6 +85,20 @@ TIME PARAMETERS: The 'query' parameter MUST use metric names that were returned by list_metrics.` + ShowTimeseriesPrompt = `Display the results as an interactive timeseries chart. + +This tool works like execute_range_query but renders the results as a visual chart in the UI clients. +Use it when the user wants to see a graph or visualization of time-series data and to use visuals to provide the answer. +Use the show_timeseries as the last tool call after all the other Prometheus tool calls where finalized. + +TIME PARAMETERS: +- 'duration': Look back from now (e.g., "5m", "1h", "24h") +- 'step': Data point resolution (e.g., "1m" for 1-hour duration, "5m" for 24-hour duration) +- 'title': A descriptive chart title (e.g., "API Error Rate Over Last Hour") +- 'description': An explanation of the chart's meaning or context (e.g., "Shows the rate of HTTP 5xx errors per second, broken down by pod") + +The 'query' parameter MUST be a range query and must use metric names that were returned by list_metrics.` + GetLabelNamesPrompt = `Get all label names (dimensions) available for filtering a metric. WHEN TO USE (after calling list_metrics): diff --git a/pkg/tools/schema.go b/pkg/tools/schema.go index 7561146..3e65148 100644 --- a/pkg/tools/schema.go +++ b/pkg/tools/schema.go @@ -131,6 +131,13 @@ type RangeQueryInput struct { Duration string `json:"duration,omitempty"` } +// ShowTimeseriesInput defines the input parameters for ShowTimeseriesHandler. +type ShowTimeseriesInput struct { + RangeQueryInput + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` +} + // InstantQueryInput defines the input parameters for ExecuteInstantQueryHandler. type InstantQueryInput struct { Query string `json:"query"` diff --git a/pkg/toolset/tools/handlers.go b/pkg/toolset/tools/handlers.go index f3ca45e..ef82076 100644 --- a/pkg/toolset/tools/handlers.go +++ b/pkg/toolset/tools/handlers.go @@ -39,6 +39,16 @@ func ExecuteRangeQueryHandler(params api.ToolHandlerParams) (*api.ToolCallResult return tools.ExecuteRangeQueryHandler(params.Context, promClient, tools.BuildRangeQueryInput(params.GetArguments()), cfg.SummarizeRangeQuery).ToToolsetResult() } +// ShowTimeseriesHandler handles the show_timeseries tool. +func ShowTimeseriesHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) { + promClient, err := getPromClient(params) + if err != nil { + return api.NewToolCallResult("", fmt.Errorf("failed to create Prometheus client: %w", err)), nil + } + + return tools.ShowTimeseriesHandler(params.Context, promClient, tools.BuildShowTimeseriesInput(params.GetArguments())).ToToolsetResult() +} + // GetLabelNamesHandler handles the retrieval of label names. func GetLabelNamesHandler(params api.ToolHandlerParams) (*api.ToolCallResult, error) { promClient, err := getPromClient(params) diff --git a/pkg/toolset/tools/tools.go b/pkg/toolset/tools/tools.go index 26f35ee..84a0119 100644 --- a/pkg/toolset/tools/tools.go +++ b/pkg/toolset/tools/tools.go @@ -27,6 +27,13 @@ func InitExecuteRangeQuery() []api.ServerTool { } } +// InitShowTimeseries creates the show_timeseries tool. +func InitShowTimeseries() []api.ServerTool { + return []api.ServerTool{ + tools.ShowTimeseries.ToServerTool(ShowTimeseriesHandler), + } +} + // InitGetLabelNames creates the get_label_names tool. func InitGetLabelNames() []api.ServerTool { return []api.ServerTool{ diff --git a/pkg/toolset/toolset.go b/pkg/toolset/toolset.go index 1ddde32..1511eb3 100644 --- a/pkg/toolset/toolset.go +++ b/pkg/toolset/toolset.go @@ -30,6 +30,7 @@ func (t *Toolset) GetTools(_ api.Openshift) []api.ServerTool { toolset_tools.InitListMetrics(), toolset_tools.InitExecuteInstantQuery(), toolset_tools.InitExecuteRangeQuery(), + toolset_tools.InitShowTimeseries(), toolset_tools.InitGetLabelNames(), toolset_tools.InitGetLabelValues(), toolset_tools.InitGetSeries(),