|
|
|
|
@@ -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
|
|
|
|
|
|