mirror of
https://github.com/k8sgpt-ai/k8sgpt.git
synced 2026-03-18 19:17:25 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2276b12b0f | ||
|
|
fd5bba6ab3 | ||
|
|
19a172e575 | ||
|
|
36de157e21 | ||
|
|
458aa9deba | ||
|
|
285c1353d5 | ||
|
|
4f63e9737c | ||
|
|
a56e4788c3 | ||
|
|
99911fbb3a | ||
|
|
abc46474e3 |
@@ -1 +1 @@
|
|||||||
{".":"0.4.28"}
|
{".":"0.4.30"}
|
||||||
25
CHANGELOG.md
25
CHANGELOG.md
@@ -1,5 +1,30 @@
|
|||||||
# Changelog
|
# 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)
|
## [0.4.28](https://github.com/k8sgpt-ai/k8sgpt/compare/v0.4.27...v0.4.28) (2026-02-15)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
We're happy that you want to contribute to this project. Please read the sections to make the process as smooth as possible.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
- Golang `1.23`
|
- Golang `1.24+`
|
||||||
- An OpenAI API key
|
- An OpenAI API key
|
||||||
* OpenAI API keys can be obtained from [OpenAI](https://platform.openai.com/account/api-keys)
|
* 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`
|
* 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._
|
_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>
|
<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-->
|
<!---x-release-please-start-version-->
|
||||||
|
|
||||||
```
|
```
|
||||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.28/k8sgpt_386.rpm
|
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_386.rpm
|
||||||
```
|
```
|
||||||
<!---x-release-please-end-->
|
<!---x-release-please-end-->
|
||||||
|
|
||||||
@@ -71,7 +75,7 @@ brew install k8sgpt
|
|||||||
|
|
||||||
<!---x-release-please-start-version-->
|
<!---x-release-please-start-version-->
|
||||||
```
|
```
|
||||||
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.28/k8sgpt_amd64.rpm
|
sudo rpm -ivh https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.30/k8sgpt_amd64.rpm
|
||||||
```
|
```
|
||||||
<!---x-release-please-end-->
|
<!---x-release-please-end-->
|
||||||
</details>
|
</details>
|
||||||
@@ -84,7 +88,7 @@ brew install k8sgpt
|
|||||||
<!---x-release-please-start-version-->
|
<!---x-release-please-start-version-->
|
||||||
|
|
||||||
```
|
```
|
||||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.28/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
|
sudo dpkg -i k8sgpt_386.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -95,7 +99,7 @@ sudo dpkg -i k8sgpt_386.deb
|
|||||||
<!---x-release-please-start-version-->
|
<!---x-release-please-start-version-->
|
||||||
|
|
||||||
```
|
```
|
||||||
curl -LO https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.28/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
|
sudo dpkg -i k8sgpt_amd64.deb
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -110,7 +114,7 @@ sudo dpkg -i k8sgpt_amd64.deb
|
|||||||
|
|
||||||
<!---x-release-please-start-version-->
|
<!---x-release-please-start-version-->
|
||||||
```
|
```
|
||||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.28/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
|
apk add --allow-untrusted k8sgpt_386.apk
|
||||||
```
|
```
|
||||||
<!---x-release-please-end-->
|
<!---x-release-please-end-->
|
||||||
@@ -119,7 +123,7 @@ sudo dpkg -i k8sgpt_amd64.deb
|
|||||||
|
|
||||||
<!---x-release-please-start-version-->
|
<!---x-release-please-start-version-->
|
||||||
```
|
```
|
||||||
wget https://github.com/k8sgpt-ai/k8sgpt/releases/download/v0.4.28/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
|
apk add --allow-untrusted k8sgpt_amd64.apk
|
||||||
```
|
```
|
||||||
<!---x-release-please-end-->
|
<!---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{
|
server := k8sgptserver.Config{
|
||||||
Backend: aiProvider.Name,
|
Backend: aiProvider.Name,
|
||||||
Port: port,
|
Port: port,
|
||||||
@@ -234,7 +239,7 @@ var ServeCmd = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
// add flag for backend
|
// add flag for backend
|
||||||
ServeCmd.Flags().StringVarP(&port, "port", "p", "8080", "Port to run the server on")
|
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().StringVarP(&backend, "backend", "b", "openai", "Backend AI provider")
|
||||||
ServeCmd.Flags().BoolVarP(&enableHttp, "http", "", false, "Enable REST/http using gppc-gateway")
|
ServeCmd.Flags().BoolVarP(&enableHttp, "http", "", false, "Enable REST/http using gppc-gateway")
|
||||||
ServeCmd.Flags().BoolVarP(&enableMCP, "mcp", "", false, "Enable Mission Control Protocol server")
|
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/component-base v0.32.2 // indirect
|
||||||
k8s.io/klog/v2 v2.130.1 // indirect
|
k8s.io/klog/v2 v2.130.1 // indirect
|
||||||
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // 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
|
oras.land/oras-go v1.2.5 // indirect
|
||||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||||
sigs.k8s.io/kustomize/api v0.18.0 // 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/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 h1:TAkag6+XfSBgkqK9I7ZvwtF0WVtUAvK8ZqTt+5zi1Us=
|
||||||
k8s.io/kubectl v0.32.2/go.mod h1:+h/NQFSPxiDZYX/WZaWw9fwYezGLISP0ud8nQKg+3g8=
|
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-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU=
|
||||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
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 h1:uUSDGlOIkdPT4svjlhi+JEnP2Ufw7AM/F5QDYiEL02U=
|
||||||
knative.dev/pkg v0.0.0-20241026180704-25f6002b00f3/go.mod h1:FeMbTLlxQqSASwlRCrYEOsZ0OKUgSj52qxhECwYCJsw=
|
knative.dev/pkg v0.0.0-20241026180704-25f6002b00f3/go.mod h1:FeMbTLlxQqSASwlRCrYEOsZ0OKUgSj52qxhECwYCJsw=
|
||||||
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package analysis
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
"reflect"
|
||||||
@@ -34,6 +35,7 @@ import (
|
|||||||
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
"github.com/k8sgpt-ai/k8sgpt/pkg/util"
|
||||||
"github.com/schollz/progressbar/v3"
|
"github.com/schollz/progressbar/v3"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Analysis struct {
|
type Analysis struct {
|
||||||
@@ -226,6 +228,15 @@ func (a *Analysis) CustomAnalyzersAreAvailable() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *Analysis) RunCustomAnalysis() {
|
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
|
var customAnalyzers []custom.CustomAnalyzer
|
||||||
if err := viper.UnmarshalKey("custom_analyzers", &customAnalyzers); err != nil {
|
if err := viper.UnmarshalKey("custom_analyzers", &customAnalyzers); err != nil {
|
||||||
a.Errors = append(a.Errors, err.Error())
|
a.Errors = append(a.Errors, err.Error())
|
||||||
@@ -526,7 +537,22 @@ func (a *Analysis) getAIResultForSanitizedFailures(texts []string, promptTmpl st
|
|||||||
// Process template.
|
// Process template.
|
||||||
prompt := fmt.Sprintf(strings.TrimSpace(promptTmpl), a.Language, inputKey)
|
prompt := fmt.Sprintf(strings.TrimSpace(promptTmpl), a.Language, inputKey)
|
||||||
if a.AIClient.GetName() == ai.CustomRestClientName {
|
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)
|
response, err := a.AIClient.GetCompletion(a.Context, prompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -29,148 +29,247 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
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
|
// handleListResources lists Kubernetes resources of a specific type
|
||||||
func (s *K8sGptMCPServer) handleListResources(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
func (s *K8sGptMCPServer) handleListResources(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
var req struct {
|
var req struct {
|
||||||
ResourceType string `json:"resourceType"`
|
ResourceType string `json:"resourceType"`
|
||||||
Namespace string `json:"namespace,omitempty"`
|
Namespace string `json:"namespace,omitempty"`
|
||||||
LabelSelector string `json:"labelSelector,omitempty"`
|
LabelSelector string `json:"labelSelector,omitempty"`
|
||||||
|
Limit int64 `json:"limit,omitempty"`
|
||||||
}
|
}
|
||||||
if err := request.BindArguments(&req); err != nil {
|
if err := request.BindArguments(&req); err != nil {
|
||||||
return mcp.NewToolResultErrorf("Failed to parse request arguments: %v", 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("", "")
|
client, err := kubernetes.NewClient("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mcp.NewToolResultErrorf("Failed to create Kubernetes client: %v", err), nil
|
return mcp.NewToolResultErrorf("Failed to create Kubernetes client: %v", err), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
listOptions := metav1.ListOptions{}
|
listOptions := metav1.ListOptions{
|
||||||
if req.LabelSelector != "" {
|
LabelSelector: req.LabelSelector,
|
||||||
listOptions.LabelSelector = req.LabelSelector
|
Limit: req.Limit,
|
||||||
}
|
}
|
||||||
|
|
||||||
var result string
|
// Get the list function from registry
|
||||||
resourceType := strings.ToLower(req.ResourceType)
|
listFunc := resourceRegistry[resourceType].list
|
||||||
|
result, err := listFunc(ctx, client, req.Namespace, listOptions)
|
||||||
switch resourceType {
|
if err != nil {
|
||||||
case "pod", "pods":
|
return mcp.NewToolResultErrorf("Failed to list %s: %v", resourceType, err), nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// 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
|
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("", "")
|
client, err := kubernetes.NewClient("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return mcp.NewToolResultErrorf("Failed to create Kubernetes client: %v", err), nil
|
return mcp.NewToolResultErrorf("Failed to create Kubernetes client: %v", err), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var result string
|
// Get the get function from registry
|
||||||
resourceType := strings.ToLower(req.ResourceType)
|
getFunc := resourceRegistry[resourceType].get
|
||||||
|
result, err := getFunc(ctx, client, req.Namespace, req.Name)
|
||||||
switch resourceType {
|
if err != nil {
|
||||||
case "pod", "pods":
|
return mcp.NewToolResultErrorf("Failed to get %s '%s': %v", resourceType, req.Name, err), nil
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// 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
|
return mcp.NewToolResultErrorf("Failed to list namespaces: %v", err), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
data, _ := json.MarshalIndent(namespaces.Items, "", " ")
|
resultJSON, err := marshalJSON(namespaces.Items)
|
||||||
return mcp.NewToolResultText(string(data)), nil
|
if err != nil {
|
||||||
|
return mcp.NewToolResultErrorf("Failed to serialize result: %v", err), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return mcp.NewToolResultText(resultJSON), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleListEvents lists Kubernetes events
|
// handleListEvents lists Kubernetes events
|
||||||
func (s *K8sGptMCPServer) handleListEvents(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
func (s *K8sGptMCPServer) handleListEvents(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
var req struct {
|
var req struct {
|
||||||
Namespace string `json:"namespace,omitempty"`
|
Namespace string `json:"namespace,omitempty"`
|
||||||
InvolvedObjectName string `json:"involvedObjectName,omitempty"`
|
InvolvedObjectName string `json:"involvedObjectName,omitempty"`
|
||||||
InvolvedObjectKind string `json:"involvedObjectKind,omitempty"`
|
InvolvedObjectKind string `json:"involvedObjectKind,omitempty"`
|
||||||
Limit int64 `json:"limit,omitempty"`
|
Limit int64 `json:"limit,omitempty"`
|
||||||
}
|
}
|
||||||
if err := request.BindArguments(&req); err != nil {
|
if err := request.BindArguments(&req); err != nil {
|
||||||
return mcp.NewToolResultErrorf("Failed to parse request arguments: %v", err), nil
|
return mcp.NewToolResultErrorf("Failed to parse request arguments: %v", err), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.Limit == 0 {
|
if req.Limit == 0 {
|
||||||
req.Limit = 100
|
req.Limit = DefaultListLimit
|
||||||
|
} else if req.Limit > MaxListLimit {
|
||||||
|
req.Limit = MaxListLimit
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := kubernetes.NewClient("", "")
|
client, err := kubernetes.NewClient("", "")
|
||||||
@@ -290,8 +380,12 @@ func (s *K8sGptMCPServer) handleListEvents(ctx context.Context, request mcp.Call
|
|||||||
filteredEvents = append(filteredEvents, event)
|
filteredEvents = append(filteredEvents, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, _ := json.MarshalIndent(filteredEvents, "", " ")
|
resultJSON, err := marshalJSON(filteredEvents)
|
||||||
return mcp.NewToolResultText(string(data)), nil
|
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
|
// 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
|
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 {
|
if req.TailLines == 0 {
|
||||||
req.TailLines = 100
|
req.TailLines = 100
|
||||||
}
|
}
|
||||||
@@ -356,8 +457,12 @@ func (s *K8sGptMCPServer) handleListFilters(ctx context.Context, request mcp.Cal
|
|||||||
"activeFilters": active,
|
"activeFilters": active,
|
||||||
}
|
}
|
||||||
|
|
||||||
data, _ := json.MarshalIndent(result, "", " ")
|
resultJSON, err := marshalJSON(result)
|
||||||
return mcp.NewToolResultText(string(data)), nil
|
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
|
// 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
|
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")
|
activeFilters := viper.GetStringSlice("active_filters")
|
||||||
|
addedFilters := []string{}
|
||||||
|
|
||||||
for _, filter := range req.Filters {
|
for _, filter := range req.Filters {
|
||||||
if !contains(activeFilters, filter) {
|
if !contains(activeFilters, filter) {
|
||||||
activeFilters = append(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.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
|
// 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
|
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")
|
activeFilters := viper.GetStringSlice("active_filters")
|
||||||
newFilters := []string{}
|
newFilters := []string{}
|
||||||
|
removedFilters := []string{}
|
||||||
|
|
||||||
for _, filter := range activeFilters {
|
for _, filter := range activeFilters {
|
||||||
if !contains(req.Filters, filter) {
|
if !contains(req.Filters, filter) {
|
||||||
newFilters = append(newFilters, 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.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
|
// handleListIntegrations lists available integrations
|
||||||
@@ -423,8 +551,12 @@ func (s *K8sGptMCPServer) handleListIntegrations(ctx context.Context, request mc
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
data, _ := json.MarshalIndent(result, "", " ")
|
resultJSON, err := marshalJSON(result)
|
||||||
return mcp.NewToolResultText(string(data)), nil
|
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
|
// contains checks if a string slice contains a specific string
|
||||||
|
|||||||
Reference in New Issue
Block a user