mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2026-03-18 19:17:25 +00:00
Compare commits
13 Commits
copilot/ad
...
renovate/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8283c8c0f3 | ||
|
|
2276b12b0f | ||
|
|
fd5bba6ab3 | ||
|
|
19a172e575 | ||
|
|
36de157e21 | ||
|
|
458aa9deba | ||
|
|
285c1353d5 | ||
|
|
4f63e9737c | ||
|
|
a56e4788c3 | ||
|
|
99911fbb3a | ||
|
|
abc46474e3 | ||
|
|
1a8f1d47a4 | ||
|
|
1f2ff98834 |
2
.github/workflows/build_container.yaml
vendored
2
.github/workflows/build_container.yaml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
- "**.md"
|
||||
|
||||
env:
|
||||
GO_VERSION: "~1.23"
|
||||
GO_VERSION: "~1.24"
|
||||
IMAGE_NAME: "k8sgpt"
|
||||
REGISTRY_IMAGE: ghcr.io/k8sgpt-ai/k8sgpt
|
||||
|
||||
|
||||
2
.github/workflows/release.yaml
vendored
2
.github/workflows/release.yaml
vendored
@@ -61,7 +61,7 @@ jobs:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: '~1.24'
|
||||
- name: Download Syft
|
||||
uses: anchore/sbom-action/download-syft@55dc4ee22412511ee8c3142cbea40418e6cec693 # v0.17.8
|
||||
- name: Run GoReleaser
|
||||
|
||||
4
.github/workflows/test.yaml
vendored
4
.github/workflows/test.yaml
vendored
@@ -9,7 +9,7 @@ on:
|
||||
- main
|
||||
|
||||
env:
|
||||
GO_VERSION: "~1.22"
|
||||
GO_VERSION: "~1.24"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -25,6 +25,6 @@ jobs:
|
||||
- name: Run test
|
||||
run: go test ./... -coverprofile=coverage.txt
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5
|
||||
uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5
|
||||
env:
|
||||
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||
|
||||
@@ -1 +1 @@
|
||||
{".":"0.4.27"}
|
||||
{".":"0.4.30"}
|
||||
47
CHANGELOG.md
47
CHANGELOG.md
@@ -1,5 +1,52 @@
|
||||
# Changelog
|
||||
|
||||
## [0.4.30](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.29...v0.4.30) (2026-02-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* validate namespace before running custom analyzers ([#1617](https://github.com/k8sgpt-ai/k8sgpt/issues/1617)) ([458aa9d](https://github.com/k8sgpt-ai/k8sgpt/commit/458aa9debac7590eb0855ffd12141b702e999a36))
|
||||
|
||||
## [0.4.29](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.28...v0.4.29) (2026-02-20)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **serve:** add short flag and env var for metrics port ([#1616](https://github.com/k8sgpt-ai/k8sgpt/issues/1616)) ([4f63e97](https://github.com/k8sgpt-ai/k8sgpt/commit/4f63e9737c6a2306686bd3b6f37e81f210665949))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update k8s.io/utils digest to b8788ab ([#1572](https://github.com/k8sgpt-ai/k8sgpt/issues/1572)) ([a56e478](https://github.com/k8sgpt-ai/k8sgpt/commit/a56e4788c3361a64df17175f163f33422a8fe606))
|
||||
* use proper JSON marshaling for customrest prompt to handle special characters ([#1615](https://github.com/k8sgpt-ai/k8sgpt/issues/1615)) ([99911fb](https://github.com/k8sgpt-ai/k8sgpt/commit/99911fbb3ac8c950fd7ee1b3210f8a9c2a6b0ad7)), closes [#1556](https://github.com/k8sgpt-ai/k8sgpt/issues/1556)
|
||||
|
||||
|
||||
### Refactoring
|
||||
|
||||
* improve MCP server handlers with better error handling and pagination ([#1613](https://github.com/k8sgpt-ai/k8sgpt/issues/1613)) ([abc4647](https://github.com/k8sgpt-ai/k8sgpt/commit/abc46474e372bcd27201f1a64372c04269acee13))
|
||||
|
||||
## [0.4.28](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.27...v0.4.28) (2026-02-15)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add Groq as LLM provider ([#1600](https://github.com/k8sgpt-ai/k8sgpt/issues/1600)) ([867bce1](https://github.com/k8sgpt-ai/k8sgpt/commit/867bce1907f5dd3387128b72c694e98091d55554))
|
||||
* multiple security fixes. Prometheus: v0.302.1 → v0.306.0 ([#1597](https://github.com/k8sgpt-ai/k8sgpt/issues/1597)) ([f5fb2a7](https://github.com/k8sgpt-ai/k8sgpt/commit/f5fb2a7e12e14fad8107940aeead5e60b064add1))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* align CI Go versions with go.mod to ensure consistency ([#1611](https://github.com/k8sgpt-ai/k8sgpt/issues/1611)) ([1f2ff98](https://github.com/k8sgpt-ai/k8sgpt/commit/1f2ff988342b8ef2aa3e3263eb845c0ee09fe24c))
|
||||
* **deps:** update module gopkg.in/yaml.v2 to v3 ([#1550](https://github.com/k8sgpt-ai/k8sgpt/issues/1550)) ([7fe3bdb](https://github.com/k8sgpt-ai/k8sgpt/commit/7fe3bdbd952bc9a1975121de5f21ad31dc1f691d))
|
||||
* use MaxCompletionTokens instead of deprecated MaxTokens for OpenAI ([#1604](https://github.com/k8sgpt-ai/k8sgpt/issues/1604)) ([c80b2e2](https://github.com/k8sgpt-ai/k8sgpt/commit/c80b2e2c346845336593ce515fe90fd501b1d0a7))
|
||||
|
||||
|
||||
### Other
|
||||
|
||||
* **deps:** update actions/checkout digest to 93cb6ef ([#1592](https://github.com/k8sgpt-ai/k8sgpt/issues/1592)) ([40ffcbe](https://github.com/k8sgpt-ai/k8sgpt/commit/40ffcbec6b65e3a99e40be5f414a3f2c087bffbb))
|
||||
* **deps:** update actions/setup-go digest to 40f1582 ([#1593](https://github.com/k8sgpt-ai/k8sgpt/issues/1593)) ([a303ffa](https://github.com/k8sgpt-ai/k8sgpt/commit/a303ffa21c7ede3dd9391185bc91fb3b4e8276b6))
|
||||
* util tests ([#1594](https://github.com/k8sgpt-ai/k8sgpt/issues/1594)) ([21369c5](https://github.com/k8sgpt-ai/k8sgpt/commit/21369c5c0917fd2b6ae4173378b2e257e2b1de7b))
|
||||
|
||||
## [0.4.27](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.26...v0.4.27) (2025-12-18)
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
We're happy that you want to contribute to this project. Please read the sections to make the process as smooth as possible.
|
||||
|
||||
## Requirements
|
||||
- Golang `1.23`
|
||||
- Golang `1.24+`
|
||||
- An OpenAI API key
|
||||
* OpenAI API keys can be obtained from [OpenAI](https://platform.openai.com/account/api-keys)
|
||||
* You can set the API key for k8sgpt using `./k8sgpt auth key`
|
||||
|
||||
16
README.md
16
README.md
@@ -21,6 +21,10 @@ It has SRE experience codified into its analyzers and helps to pull out the most
|
||||
|
||||
_Out of the box integration with OpenAI, Azure, Cohere, Amazon Bedrock, Google Gemini and local models._
|
||||
|
||||
|
||||
> **Sister project:** Check out [sympozium](https://github.com/AlexsJones/sympozium/) for managing agents in Kubernetes.
|
||||
|
||||
|
||||
<a href="https://www.producthunt.com/posts/k8sgpt?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-k8sgpt" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=389489&theme=light" alt="K8sGPT - K8sGPT gives Kubernetes Superpowers to everyone | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://hellogithub.com/repository/9dfe44c18dfb4d6fa0181baf8b2cf2e1" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=9dfe44c18dfb4d6fa0181baf8b2cf2e1&claim_uid=gqG4wmzkMrP0eFy" alt="Featured|HelloGitHub" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
|
||||
@@ -63,7 +67,7 @@ brew install k8sgpt
|
||||
<!---x-release-please-start-version-->
|
||||
|
||||
```
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.27/k8sgpt_386.rpm
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_386.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
|
||||
@@ -71,7 +75,7 @@ brew install k8sgpt
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.27/k8sgpt_amd64.rpm
|
||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_amd64.rpm
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
</details>
|
||||
@@ -84,7 +88,7 @@ brew install k8sgpt
|
||||
<!---x-release-please-start-version-->
|
||||
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.27/k8sgpt_386.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_386.deb
|
||||
sudo dpkg -i k8sgpt_386.deb
|
||||
```
|
||||
|
||||
@@ -95,7 +99,7 @@ sudo dpkg -i k8sgpt_386.deb
|
||||
<!---x-release-please-start-version-->
|
||||
|
||||
```
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.27/k8sgpt_amd64.deb
|
||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_amd64.deb
|
||||
sudo dpkg -i k8sgpt_amd64.deb
|
||||
```
|
||||
|
||||
@@ -110,7 +114,7 @@ sudo dpkg -i k8sgpt_amd64.deb
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.27/k8sgpt_386.apk
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_386.apk
|
||||
apk add --allow-untrusted k8sgpt_386.apk
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
@@ -119,7 +123,7 @@ sudo dpkg -i k8sgpt_amd64.deb
|
||||
|
||||
<!---x-release-please-start-version-->
|
||||
```
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.27/k8sgpt_amd64.apk
|
||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_amd64.apk
|
||||
apk add --allow-untrusted k8sgpt_amd64.apk
|
||||
```
|
||||
<!---x-release-please-end-->
|
||||
|
||||
@@ -203,6 +203,11 @@ var ServeCmd = &cobra.Command{
|
||||
}()
|
||||
}
|
||||
|
||||
// Allow metrics port to be overridden by environment variable
|
||||
if envMetricsPort := os.Getenv("K8SGPT_METRICS_PORT"); envMetricsPort != "" && !cmd.Flags().Changed("metrics-port") {
|
||||
metricsPort = envMetricsPort
|
||||
}
|
||||
|
||||
server := k8sgptserver.Config{
|
||||
Backend: aiProvider.Name,
|
||||
Port: port,
|
||||
@@ -234,7 +239,7 @@ var ServeCmd = &cobra.Command{
|
||||
func init() {
|
||||
// add flag for backend
|
||||
ServeCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port to run the server on")
|
||||
ServeCmd.Flags().StringVarP(&metricsPort, "metrics-port", "", "8081", "Port to run the metrics-server on")
|
||||
ServeCmd.Flags().StringVarP(&metricsPort, "metrics-port", "m", "8081", "Port to run the metrics-server on (env: K8SGPT_METRICS_PORT)")
|
||||
ServeCmd.Flags().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
|
||||
ServeCmd.Flags().BoolVarP(&enableHttp, "http", "", false, "Enable REST/http using gppc-gateway")
|
||||
ServeCmd.Flags().BoolVarP(&enableMCP, "mcp", "", false, "Enable Mission Control Protocol server")
|
||||
|
||||
2
go.mod
2
go.mod
@@ -282,7 +282,7 @@ require (
|
||||
k8s.io/component-base v0.32.2 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2
|
||||
oras.land/oras-go v1.2.5 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.18.0 // indirect
|
||||
|
||||
4
go.sum
4
go.sum
@@ -2260,8 +2260,8 @@ k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJ
|
||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f/go.mod h1:R/HEjbvWI0qdfb8viZUeVZm0X6IZnxAydC7YU42CMw4=
|
||||
k8s.io/kubectl v0.32.2 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us=
|
||||
k8s.io/kubectl v0.32.2/go.mod h1:+h/NQFSPxiDZYX/WZaWw9fwYezGLISP0ud8nQKg+3g8=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 h1:hwvWFiBzdWw1FhfY1FooPn3kzWuJ8tmbZBHi4zVsl1Y=
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
||||
k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
knative.dev/pkg v0.0.0-20241026180704-25f6002b00f3 h1:uUSDGlOIkdPT4svjlhi+JEnP2Ufw7AM/F5QDYiEL02U=
|
||||
knative.dev/pkg v0.0.0-20241026180704-25f6002b00f3/go.mod h1:FeMbTLlxQqSASwlRCrYEOsZ0OKUgSj52qxhECwYCJsw=
|
||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
|
||||
@@ -16,6 +16,7 @@ package analysis
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
@@ -34,6 +35,7 @@ import (
|
||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||
"github.com/schollz/progressbar/v3"
|
||||
"github.com/spf13/viper"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type Analysis struct {
|
||||
@@ -226,6 +228,15 @@ func (a *Analysis) CustomAnalyzersAreAvailable() bool {
|
||||
}
|
||||
|
||||
func (a *Analysis) RunCustomAnalysis() {
|
||||
// Validate namespace if specified, consistent with built-in filter behavior
|
||||
if a.Namespace != "" && a.Client != nil {
|
||||
_, err := a.Client.Client.CoreV1().Namespaces().Get(a.Context, a.Namespace, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
a.Errors = append(a.Errors, fmt.Sprintf("namespace %q not found: %s", a.Namespace, err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
var customAnalyzers []custom.CustomAnalyzer
|
||||
if err := viper.UnmarshalKey("custom_analyzers", &customAnalyzers); err != nil {
|
||||
a.Errors = append(a.Errors, err.Error())
|
||||
@@ -526,7 +537,22 @@ func (a *Analysis) getAIResultForSanitizedFailures(texts []string, promptTmpl st
|
||||
// Process template.
|
||||
prompt := fmt.Sprintf(strings.TrimSpace(promptTmpl), a.Language, inputKey)
|
||||
if a.AIClient.GetName() == ai.CustomRestClientName {
|
||||
prompt = fmt.Sprintf(ai.PromptMap["raw"], a.Language, inputKey, prompt)
|
||||
// Use proper JSON marshaling to handle special characters in error messages
|
||||
// This fixes issues with quotes, newlines, and other special chars in inputKey
|
||||
customRestPrompt := struct {
|
||||
Language string `json:"language"`
|
||||
Message string `json:"message"`
|
||||
Prompt string `json:"prompt"`
|
||||
}{
|
||||
Language: a.Language,
|
||||
Message: inputKey,
|
||||
Prompt: prompt,
|
||||
}
|
||||
promptBytes, err := json.Marshal(customRestPrompt)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal customrest prompt: %w", err)
|
||||
}
|
||||
prompt = string(promptBytes)
|
||||
}
|
||||
response, err := a.AIClient.GetCompletion(a.Context, prompt)
|
||||
if err != nil {
|
||||
|
||||
@@ -29,148 +29,247 @@ import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultListLimit is the default maximum number of resources to return
|
||||
DefaultListLimit = 100
|
||||
// MaxListLimit is the maximum allowed limit for list operations
|
||||
MaxListLimit = 1000
|
||||
)
|
||||
|
||||
// resourceLister defines a function that lists Kubernetes resources
|
||||
type resourceLister func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error)
|
||||
|
||||
// resourceGetter defines a function that gets a single Kubernetes resource
|
||||
type resourceGetter func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error)
|
||||
|
||||
// resourceRegistry maps resource types to their list and get functions
|
||||
var resourceRegistry = map[string]struct {
|
||||
list resourceLister
|
||||
get resourceGetter
|
||||
}{
|
||||
"pod": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.CoreV1().Pods(namespace).List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.CoreV1().Pods(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"deployment": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.AppsV1().Deployments(namespace).List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.AppsV1().Deployments(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"service": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.CoreV1().Services(namespace).List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"node": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.CoreV1().Nodes().List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.CoreV1().Nodes().Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"job": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.BatchV1().Jobs(namespace).List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.BatchV1().Jobs(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"cronjob": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.BatchV1().CronJobs(namespace).List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.BatchV1().CronJobs(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"statefulset": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.AppsV1().StatefulSets(namespace).List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.AppsV1().StatefulSets(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"daemonset": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.AppsV1().DaemonSets(namespace).List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.AppsV1().DaemonSets(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"replicaset": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.AppsV1().ReplicaSets(namespace).List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.AppsV1().ReplicaSets(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"configmap": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.CoreV1().ConfigMaps(namespace).List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.CoreV1().ConfigMaps(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"secret": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.CoreV1().Secrets(namespace).List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.CoreV1().Secrets(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"ingress": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.NetworkingV1().Ingresses(namespace).List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.NetworkingV1().Ingresses(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"persistentvolumeclaim": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.CoreV1().PersistentVolumeClaims(namespace).List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.CoreV1().PersistentVolumeClaims(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
"persistentvolume": {
|
||||
list: func(ctx context.Context, client *kubernetes.Client, namespace string, opts metav1.ListOptions) (interface{}, error) {
|
||||
return client.Client.CoreV1().PersistentVolumes().List(ctx, opts)
|
||||
},
|
||||
get: func(ctx context.Context, client *kubernetes.Client, namespace, name string) (interface{}, error) {
|
||||
return client.Client.CoreV1().PersistentVolumes().Get(ctx, name, metav1.GetOptions{})
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Resource type aliases for convenience
|
||||
var resourceTypeAliases = map[string]string{
|
||||
"pods": "pod",
|
||||
"deployments": "deployment",
|
||||
"services": "service",
|
||||
"svc": "service",
|
||||
"nodes": "node",
|
||||
"jobs": "job",
|
||||
"cronjobs": "cronjob",
|
||||
"statefulsets": "statefulset",
|
||||
"sts": "statefulset",
|
||||
"daemonsets": "daemonset",
|
||||
"ds": "daemonset",
|
||||
"replicasets": "replicaset",
|
||||
"rs": "replicaset",
|
||||
"configmaps": "configmap",
|
||||
"cm": "configmap",
|
||||
"secrets": "secret",
|
||||
"ingresses": "ingress",
|
||||
"ing": "ingress",
|
||||
"persistentvolumeclaims": "persistentvolumeclaim",
|
||||
"pvc": "persistentvolumeclaim",
|
||||
"persistentvolumes": "persistentvolume",
|
||||
"pv": "persistentvolume",
|
||||
}
|
||||
|
||||
// normalizeResourceType converts resource type variants to canonical form
|
||||
func normalizeResourceType(resourceType string) (string, error) {
|
||||
normalized := strings.ToLower(resourceType)
|
||||
|
||||
// Check if it's an alias
|
||||
if canonical, ok := resourceTypeAliases[normalized]; ok {
|
||||
normalized = canonical
|
||||
}
|
||||
|
||||
// Check if it's a known resource type
|
||||
if _, ok := resourceRegistry[normalized]; !ok {
|
||||
return "", fmt.Errorf("unsupported resource type: %s", resourceType)
|
||||
}
|
||||
|
||||
return normalized, nil
|
||||
}
|
||||
|
||||
// marshalJSON marshals data to JSON with proper error handling
|
||||
func marshalJSON(data interface{}) (string, error) {
|
||||
jsonData, err := json.MarshalIndent(data, "", " ")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to marshal JSON: %w", err)
|
||||
}
|
||||
return string(jsonData), nil
|
||||
}
|
||||
|
||||
// handleListResources lists Kubernetes resources of a specific type
|
||||
func (s *K8sGptMCPServer) handleListResources(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
var req struct {
|
||||
ResourceType string `json:"resourceType"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
LabelSelector string `json:"labelSelector,omitempty"`
|
||||
Limit int64 `json:"limit,omitempty"`
|
||||
}
|
||||
if err := request.BindArguments(&req); err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to parse request arguments: %v", err), nil
|
||||
}
|
||||
|
||||
if req.ResourceType == "" {
|
||||
return mcp.NewToolResultErrorf("resourceType is required"), nil
|
||||
}
|
||||
|
||||
// Normalize and validate resource type
|
||||
resourceType, err := normalizeResourceType(req.ResourceType)
|
||||
if err != nil {
|
||||
supportedTypes := make([]string, 0, len(resourceRegistry))
|
||||
for key := range resourceRegistry {
|
||||
supportedTypes = append(supportedTypes, key)
|
||||
}
|
||||
return mcp.NewToolResultErrorf("%v. Supported types: %v", err, supportedTypes), nil
|
||||
}
|
||||
|
||||
// Set default and validate limit
|
||||
if req.Limit == 0 {
|
||||
req.Limit = DefaultListLimit
|
||||
} else if req.Limit > MaxListLimit {
|
||||
req.Limit = MaxListLimit
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewClient("", "")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to create Kubernetes client: %v", err), nil
|
||||
}
|
||||
|
||||
listOptions := metav1.ListOptions{}
|
||||
if req.LabelSelector != "" {
|
||||
listOptions.LabelSelector = req.LabelSelector
|
||||
listOptions := metav1.ListOptions{
|
||||
LabelSelector: req.LabelSelector,
|
||||
Limit: req.Limit,
|
||||
}
|
||||
|
||||
var result string
|
||||
resourceType := strings.ToLower(req.ResourceType)
|
||||
|
||||
switch resourceType {
|
||||
case "pod", "pods":
|
||||
pods, err := client.Client.CoreV1().Pods(req.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list pods: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(pods.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "deployment", "deployments":
|
||||
deps, err := client.Client.AppsV1().Deployments(req.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list deployments: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(deps.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "service", "services", "svc":
|
||||
svcs, err := client.Client.CoreV1().Services(req.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list services: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(svcs.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "node", "nodes":
|
||||
nodes, err := client.Client.CoreV1().Nodes().List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list nodes: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(nodes.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "job", "jobs":
|
||||
jobs, err := client.Client.BatchV1().Jobs(req.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list jobs: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(jobs.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "cronjob", "cronjobs":
|
||||
cronjobs, err := client.Client.BatchV1().CronJobs(req.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list cronjobs: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(cronjobs.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "statefulset", "statefulsets", "sts":
|
||||
sts, err := client.Client.AppsV1().StatefulSets(req.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list statefulsets: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(sts.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "daemonset", "daemonsets", "ds":
|
||||
ds, err := client.Client.AppsV1().DaemonSets(req.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list daemonsets: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(ds.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "replicaset", "replicasets", "rs":
|
||||
rs, err := client.Client.AppsV1().ReplicaSets(req.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list replicasets: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(rs.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "configmap", "configmaps", "cm":
|
||||
cms, err := client.Client.CoreV1().ConfigMaps(req.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list configmaps: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(cms.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "secret", "secrets":
|
||||
secrets, err := client.Client.CoreV1().Secrets(req.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list secrets: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(secrets.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "ingress", "ingresses", "ing":
|
||||
ingresses, err := client.Client.NetworkingV1().Ingresses(req.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list ingresses: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(ingresses.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "persistentvolumeclaim", "persistentvolumeclaims", "pvc":
|
||||
pvcs, err := client.Client.CoreV1().PersistentVolumeClaims(req.Namespace).List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list PVCs: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(pvcs.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "persistentvolume", "persistentvolumes", "pv":
|
||||
pvs, err := client.Client.CoreV1().PersistentVolumes().List(ctx, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list PVs: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(pvs.Items, "", " ")
|
||||
result = string(data)
|
||||
|
||||
default:
|
||||
return mcp.NewToolResultErrorf("Unsupported resource type: %s. Supported types: pods, deployments, services, nodes, jobs, cronjobs, statefulsets, daemonsets, replicasets, configmaps, secrets, ingresses, pvc, pv", resourceType), nil
|
||||
// Get the list function from registry
|
||||
listFunc := resourceRegistry[resourceType].list
|
||||
result, err := listFunc(ctx, client, req.Namespace, listOptions)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to list %s: %v", resourceType, err), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(result), nil
|
||||
// Extract items from the result (all list types have an Items field)
|
||||
resultJSON, err := marshalJSON(result)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to serialize result: %v", err), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(resultJSON), nil
|
||||
}
|
||||
|
||||
// handleGetResource gets detailed information about a specific resource
|
||||
@@ -184,52 +283,37 @@ func (s *K8sGptMCPServer) handleGetResource(ctx context.Context, request mcp.Cal
|
||||
return mcp.NewToolResultErrorf("Failed to parse request arguments: %v", err), nil
|
||||
}
|
||||
|
||||
if req.ResourceType == "" {
|
||||
return mcp.NewToolResultErrorf("resourceType is required"), nil
|
||||
}
|
||||
if req.Name == "" {
|
||||
return mcp.NewToolResultErrorf("name is required"), nil
|
||||
}
|
||||
|
||||
// Normalize and validate resource type
|
||||
resourceType, err := normalizeResourceType(req.ResourceType)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("%v", err), nil
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewClient("", "")
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to create Kubernetes client: %v", err), nil
|
||||
}
|
||||
|
||||
var result string
|
||||
resourceType := strings.ToLower(req.ResourceType)
|
||||
|
||||
switch resourceType {
|
||||
case "pod", "pods":
|
||||
pod, err := client.Client.CoreV1().Pods(req.Namespace).Get(ctx, req.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to get pod: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(pod, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "deployment", "deployments":
|
||||
dep, err := client.Client.AppsV1().Deployments(req.Namespace).Get(ctx, req.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to get deployment: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(dep, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "service", "services", "svc":
|
||||
svc, err := client.Client.CoreV1().Services(req.Namespace).Get(ctx, req.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to get service: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(svc, "", " ")
|
||||
result = string(data)
|
||||
|
||||
case "node", "nodes":
|
||||
node, err := client.Client.CoreV1().Nodes().Get(ctx, req.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to get node: %v", err), nil
|
||||
}
|
||||
data, _ := json.MarshalIndent(node, "", " ")
|
||||
result = string(data)
|
||||
|
||||
default:
|
||||
return mcp.NewToolResultErrorf("Unsupported resource type: %s", resourceType), nil
|
||||
// Get the get function from registry
|
||||
getFunc := resourceRegistry[resourceType].get
|
||||
result, err := getFunc(ctx, client, req.Namespace, req.Name)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to get %s '%s': %v", resourceType, req.Name, err), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(result), nil
|
||||
resultJSON, err := marshalJSON(result)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to serialize result: %v", err), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(resultJSON), nil
|
||||
}
|
||||
|
||||
// handleListNamespaces lists all namespaces in the cluster
|
||||
@@ -244,24 +328,30 @@ func (s *K8sGptMCPServer) handleListNamespaces(ctx context.Context, request mcp.
|
||||
return mcp.NewToolResultErrorf("Failed to list namespaces: %v", err), nil
|
||||
}
|
||||
|
||||
data, _ := json.MarshalIndent(namespaces.Items, "", " ")
|
||||
return mcp.NewToolResultText(string(data)), nil
|
||||
resultJSON, err := marshalJSON(namespaces.Items)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to serialize result: %v", err), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(resultJSON), nil
|
||||
}
|
||||
|
||||
// handleListEvents lists Kubernetes events
|
||||
func (s *K8sGptMCPServer) handleListEvents(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
var req struct {
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
InvolvedObjectName string `json:"involvedObjectName,omitempty"`
|
||||
InvolvedObjectKind string `json:"involvedObjectKind,omitempty"`
|
||||
Limit int64 `json:"limit,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
InvolvedObjectName string `json:"involvedObjectName,omitempty"`
|
||||
InvolvedObjectKind string `json:"involvedObjectKind,omitempty"`
|
||||
Limit int64 `json:"limit,omitempty"`
|
||||
}
|
||||
if err := request.BindArguments(&req); err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to parse request arguments: %v", err), nil
|
||||
}
|
||||
|
||||
if req.Limit == 0 {
|
||||
req.Limit = 100
|
||||
req.Limit = DefaultListLimit
|
||||
} else if req.Limit > MaxListLimit {
|
||||
req.Limit = MaxListLimit
|
||||
}
|
||||
|
||||
client, err := kubernetes.NewClient("", "")
|
||||
@@ -290,8 +380,12 @@ func (s *K8sGptMCPServer) handleListEvents(ctx context.Context, request mcp.Call
|
||||
filteredEvents = append(filteredEvents, event)
|
||||
}
|
||||
|
||||
data, _ := json.MarshalIndent(filteredEvents, "", " ")
|
||||
return mcp.NewToolResultText(string(data)), nil
|
||||
resultJSON, err := marshalJSON(filteredEvents)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to serialize result: %v", err), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(resultJSON), nil
|
||||
}
|
||||
|
||||
// handleGetLogs retrieves logs from a pod container
|
||||
@@ -308,6 +402,13 @@ func (s *K8sGptMCPServer) handleGetLogs(ctx context.Context, request mcp.CallToo
|
||||
return mcp.NewToolResultErrorf("Failed to parse request arguments: %v", err), nil
|
||||
}
|
||||
|
||||
if req.PodName == "" {
|
||||
return mcp.NewToolResultErrorf("podName is required"), nil
|
||||
}
|
||||
if req.Namespace == "" {
|
||||
return mcp.NewToolResultErrorf("namespace is required"), nil
|
||||
}
|
||||
|
||||
if req.TailLines == 0 {
|
||||
req.TailLines = 100
|
||||
}
|
||||
@@ -356,8 +457,12 @@ func (s *K8sGptMCPServer) handleListFilters(ctx context.Context, request mcp.Cal
|
||||
"activeFilters": active,
|
||||
}
|
||||
|
||||
data, _ := json.MarshalIndent(result, "", " ")
|
||||
return mcp.NewToolResultText(string(data)), nil
|
||||
resultJSON, err := marshalJSON(result)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to serialize result: %v", err), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(resultJSON), nil
|
||||
}
|
||||
|
||||
// handleAddFilters adds filters to enable specific analyzers
|
||||
@@ -369,10 +474,17 @@ func (s *K8sGptMCPServer) handleAddFilters(ctx context.Context, request mcp.Call
|
||||
return mcp.NewToolResultErrorf("Failed to parse request arguments: %v", err), nil
|
||||
}
|
||||
|
||||
if len(req.Filters) == 0 {
|
||||
return mcp.NewToolResultErrorf("filters array is required and cannot be empty"), nil
|
||||
}
|
||||
|
||||
activeFilters := viper.GetStringSlice("active_filters")
|
||||
addedFilters := []string{}
|
||||
|
||||
for _, filter := range req.Filters {
|
||||
if !contains(activeFilters, filter) {
|
||||
activeFilters = append(activeFilters, filter)
|
||||
addedFilters = append(addedFilters, filter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,7 +493,11 @@ func (s *K8sGptMCPServer) handleAddFilters(ctx context.Context, request mcp.Call
|
||||
return mcp.NewToolResultErrorf("Failed to save configuration: %v", err), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully added filters: %v", req.Filters)), nil
|
||||
if len(addedFilters) == 0 {
|
||||
return mcp.NewToolResultText("All specified filters were already active"), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully added filters: %v", addedFilters)), nil
|
||||
}
|
||||
|
||||
// handleRemoveFilters removes filters to disable specific analyzers
|
||||
@@ -393,11 +509,19 @@ func (s *K8sGptMCPServer) handleRemoveFilters(ctx context.Context, request mcp.C
|
||||
return mcp.NewToolResultErrorf("Failed to parse request arguments: %v", err), nil
|
||||
}
|
||||
|
||||
if len(req.Filters) == 0 {
|
||||
return mcp.NewToolResultErrorf("filters array is required and cannot be empty"), nil
|
||||
}
|
||||
|
||||
activeFilters := viper.GetStringSlice("active_filters")
|
||||
newFilters := []string{}
|
||||
removedFilters := []string{}
|
||||
|
||||
for _, filter := range activeFilters {
|
||||
if !contains(req.Filters, filter) {
|
||||
newFilters = append(newFilters, filter)
|
||||
} else {
|
||||
removedFilters = append(removedFilters, filter)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -406,7 +530,11 @@ func (s *K8sGptMCPServer) handleRemoveFilters(ctx context.Context, request mcp.C
|
||||
return mcp.NewToolResultErrorf("Failed to save configuration: %v", err), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully removed filters: %v", req.Filters)), nil
|
||||
if len(removedFilters) == 0 {
|
||||
return mcp.NewToolResultText("None of the specified filters were active"), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(fmt.Sprintf("Successfully removed filters: %v", removedFilters)), nil
|
||||
}
|
||||
|
||||
// handleListIntegrations lists available integrations
|
||||
@@ -423,8 +551,12 @@ func (s *K8sGptMCPServer) handleListIntegrations(ctx context.Context, request mc
|
||||
})
|
||||
}
|
||||
|
||||
data, _ := json.MarshalIndent(result, "", " ")
|
||||
return mcp.NewToolResultText(string(data)), nil
|
||||
resultJSON, err := marshalJSON(result)
|
||||
if err != nil {
|
||||
return mcp.NewToolResultErrorf("Failed to serialize result: %v", err), nil
|
||||
}
|
||||
|
||||
return mcp.NewToolResultText(resultJSON), nil
|
||||
}
|
||||
|
||||
// contains checks if a string slice contains a specific string
|
||||
|
||||
Reference in New Issue
Block a user