mcp-maigret
by BurtTheCoder
- internal
- tools
package tools
import (
"context"
"fmt"
"strings"
"github.com/strowk/foxy-contexts/pkg/fxctx"
"github.com/strowk/foxy-contexts/pkg/mcp"
"github.com/strowk/foxy-contexts/pkg/toolinput"
"github.com/strowk/mcp-k8s-go/internal/content"
"github.com/strowk/mcp-k8s-go/internal/k8s"
"github.com/strowk/mcp-k8s-go/internal/k8s/apps/v1/deployment"
"github.com/strowk/mcp-k8s-go/internal/k8s/core/v1/pod"
"github.com/strowk/mcp-k8s-go/internal/k8s/core/v1/service"
"github.com/strowk/mcp-k8s-go/internal/utils"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/discovery/cached/memory"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/restmapper"
)
func NewListResourcesTool(pool k8s.ClientPool) fxctx.Tool {
contextProperty := "context"
namespaceProperty := "namespace"
kindProperty := "kind"
groupProperty := "group"
versionProperty := "version"
inputSchema := toolinput.NewToolInputSchema(
toolinput.WithString(contextProperty, "Name of the Kubernetes context to use, defaults to current context"),
toolinput.WithString(namespaceProperty, "Namespace to list resources from, defaults to all namespaces"),
toolinput.WithString(groupProperty, "API Group of resources to list"),
toolinput.WithString(versionProperty, "API Version of resources to list"),
toolinput.WithRequiredString(kindProperty, "Kind of resources to list"),
)
return fxctx.NewTool(
&mcp.Tool{
Name: "list-k8s-resources",
Description: utils.Ptr("List arbitrary Kubernetes resources"),
InputSchema: inputSchema.GetMcpToolInputSchema(),
},
func(args map[string]interface{}) *mcp.CallToolResult {
input, err := inputSchema.Validate(args)
if err != nil {
return utils.ErrResponse(err)
}
k8sCtx := input.StringOr(contextProperty, "")
namespace := input.StringOr(namespaceProperty, metav1.NamespaceAll)
kind, err := input.String(kindProperty)
if err != nil {
return utils.ErrResponse(err)
}
group := input.StringOr(groupProperty, "")
version := input.StringOr(versionProperty, "")
clientset, err := pool.GetClientset(k8sCtx)
if err != nil {
return utils.ErrResponse(err)
}
if strings.ToLower(kind) == "deployment" && (group == "apps" || group == "") && (version == "v1" || version == "") {
return deployment.ListDeployments(clientset, namespace)
}
if strings.ToLower(kind) == "pod" && (group == "" || group == "core") && (version == "" || version == "v1") {
return pod.ListPods(clientset, namespace)
}
if strings.ToLower(kind) == "service" && (group == "" || group == "core") && (version == "" || version == "v1") {
return service.ListServices(clientset, namespace)
}
res, err := clientset.Discovery().ServerPreferredResources()
if res == nil && err != nil {
return utils.ErrResponse(err)
}
cfg := k8s.GetKubeConfigForContext(k8sCtx)
restConfig, err := cfg.ClientConfig()
if err != nil {
return utils.ErrResponse(err)
}
dynClient, err := dynamic.NewForConfig(restConfig)
if err != nil {
return utils.ErrResponse(err)
}
mapper := restmapper.NewDeferredDiscoveryRESTMapper(memory.NewMemCacheClient(clientset.Discovery()))
for _, r := range res {
for _, apiResource := range r.APIResources {
if strings.EqualFold(apiResource.Kind, kind) && (group == "" || strings.EqualFold(apiResource.Group, group)) && (version == "" || strings.EqualFold(apiResource.Version, version)) {
gvk := schema.GroupVersionKind{
Group: apiResource.Group,
Version: apiResource.Version,
Kind: apiResource.Kind,
}
mapping, err := mapper.RESTMapping(gvk.GroupKind(), gvk.Version)
if err != nil {
return utils.ErrResponse(err)
}
unstructured, err := dynClient.Resource(mapping.Resource).Namespace(namespace).List(context.Background(), metav1.ListOptions{})
if err != nil {
return utils.ErrResponse(err)
}
var contents []interface{} = make([]interface{}, len(unstructured.Items))
for i, item := range unstructured.Items {
// we try to list only metadata to avoid too big outputs
metadata, ok := item.Object["metadata"].(map[string]interface{})
if !ok {
cnt, err := content.NewJsonContent(metadata)
if err != nil {
return utils.ErrResponse(err)
}
contents[i] = cnt
continue
}
listContent := ListContent{}
if name, ok := metadata["name"].(string); ok {
listContent.Name = name
}
if namespace, ok := metadata["namespace"].(string); ok {
listContent.Namespace = namespace
}
cnt, err := content.NewJsonContent(listContent)
if err != nil {
return utils.ErrResponse(err)
}
contents[i] = cnt
}
return &mcp.CallToolResult{
Meta: map[string]interface{}{},
Content: contents,
IsError: utils.Ptr(false),
}
}
}
}
return utils.ErrResponse(fmt.Errorf("resource %s/%s/%s not found", group, version, kind))
},
)
}
type ListContent struct {
Name string `json:"name"`
Namespace string `json:"namespace"`
}