mirror of
https://github.com/openshift/openshift-mcp-server.git
synced 2025-10-17 14:27:48 +03:00
feat(kubernetes): resources_get can get any resource in the cluster
This commit is contained in:
@@ -25,6 +25,25 @@ func (s *Sever) initResources() {
|
||||
mcp.Description("Optional Namespace to retrieve the namespaced resources from (ignored in case of cluster scoped resources). If not provided, will list resources from all namespaces"),
|
||||
),
|
||||
), resourcesList)
|
||||
s.server.AddTool(mcp.NewTool(
|
||||
"resources_get",
|
||||
mcp.WithDescription("Get a Kubernetes resource in the current cluster by providing its apiVersion, kind, optionally the namespace, and its name"),
|
||||
mcp.WithString("apiVersion",
|
||||
mcp.Description("apiVersion of the resource (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)"),
|
||||
mcp.Required(),
|
||||
),
|
||||
mcp.WithString("kind",
|
||||
mcp.Description("kind of the resource (examples of valid kind are: Pod, Service, Deployment, Ingress)"),
|
||||
mcp.Required(),
|
||||
),
|
||||
mcp.WithString("namespace",
|
||||
mcp.Description("Optional Namespace to retrieve the namespaced resource from (ignored in case of cluster scoped resources). If not provided, will get resource from configured namespace"),
|
||||
),
|
||||
mcp.WithString("name",
|
||||
mcp.Description("Name of the resource"),
|
||||
mcp.Required(),
|
||||
),
|
||||
), resourcesGet)
|
||||
s.server.AddTool(mcp.NewTool(
|
||||
"resources_create_or_update",
|
||||
mcp.WithDescription("Create or update a Kubernetes resource in the current cluster by providing a YAML or JSON representation of the resource"),
|
||||
@@ -38,31 +57,47 @@ func (s *Sever) initResources() {
|
||||
func resourcesList(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
k, err := kubernetes.NewKubernetes()
|
||||
if err != nil {
|
||||
return NewTextResult("", fmt.Errorf("failed to create or update resources: %v", err)), nil
|
||||
}
|
||||
apiVersion := ctr.Params.Arguments["apiVersion"]
|
||||
if apiVersion == nil {
|
||||
return NewTextResult("", errors.New("failed to list resources, missing argument apiVersion")), nil
|
||||
}
|
||||
kind := ctr.Params.Arguments["kind"]
|
||||
if kind == nil {
|
||||
return NewTextResult("", errors.New("failed to list resources, missing argument kind")), nil
|
||||
return NewTextResult("", fmt.Errorf("failed to list resources: %v", err)), nil
|
||||
}
|
||||
namespace := ctr.Params.Arguments["namespace"]
|
||||
if namespace == nil {
|
||||
namespace = ""
|
||||
}
|
||||
gv, err := schema.ParseGroupVersion(apiVersion.(string))
|
||||
gvk, err := parseGroupVersionKind(ctr.Params.Arguments)
|
||||
if err != nil {
|
||||
return NewTextResult("", errors.New("failed to list resources, invalid argument apiVersion")), nil
|
||||
return NewTextResult("", fmt.Errorf("failed to list resources, %s", err)), nil
|
||||
}
|
||||
ret, err := k.ResourcesList(ctx, &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind.(string)}, namespace.(string))
|
||||
ret, err := k.ResourcesList(ctx, gvk, namespace.(string))
|
||||
if err != nil {
|
||||
return NewTextResult("", fmt.Errorf("failed to list resources: %v", err)), nil
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
}
|
||||
|
||||
func resourcesGet(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
k, err := kubernetes.NewKubernetes()
|
||||
if err != nil {
|
||||
return NewTextResult("", fmt.Errorf("failed to get resource: %v", err)), nil
|
||||
}
|
||||
namespace := ctr.Params.Arguments["namespace"]
|
||||
if namespace == nil {
|
||||
namespace = ""
|
||||
}
|
||||
gvk, err := parseGroupVersionKind(ctr.Params.Arguments)
|
||||
if err != nil {
|
||||
return NewTextResult("", fmt.Errorf("failed to get resource, %s", err)), nil
|
||||
}
|
||||
name := ctr.Params.Arguments["name"]
|
||||
if name == nil {
|
||||
return NewTextResult("", errors.New("failed to get resource, missing argument name")), nil
|
||||
}
|
||||
ret, err := k.ResourcesGet(ctx, gvk, namespace.(string), name.(string))
|
||||
if err != nil {
|
||||
return NewTextResult("", fmt.Errorf("failed to get resource: %v", err)), nil
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
}
|
||||
|
||||
func resourcesCreateOrUpdate(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
k, err := kubernetes.NewKubernetes()
|
||||
if err != nil {
|
||||
@@ -78,3 +113,19 @@ func resourcesCreateOrUpdate(ctx context.Context, ctr mcp.CallToolRequest) (*mcp
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
}
|
||||
|
||||
func parseGroupVersionKind(arguments map[string]interface{}) (*schema.GroupVersionKind, error) {
|
||||
apiVersion := arguments["apiVersion"]
|
||||
if apiVersion == nil {
|
||||
return nil, errors.New("missing argument apiVersion")
|
||||
}
|
||||
kind := arguments["kind"]
|
||||
if kind == nil {
|
||||
return nil, errors.New("missing argument kind")
|
||||
}
|
||||
gv, err := schema.ParseGroupVersion(apiVersion.(string))
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid argument apiVersion")
|
||||
}
|
||||
return &schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: kind.(string)}, nil
|
||||
}
|
||||
|
||||
@@ -85,6 +85,92 @@ func TestResourcesList(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestResourcesGet(t *testing.T) {
|
||||
testCase(t, func(c *mcpContext) {
|
||||
c.withEnvTest()
|
||||
t.Run("resources_get with missing apiVersion returns error", func(t *testing.T) {
|
||||
toolResult, _ := c.callTool("resources_get", map[string]interface{}{})
|
||||
if !toolResult.IsError {
|
||||
t.Fatalf("call tool should fail")
|
||||
return
|
||||
}
|
||||
if toolResult.Content[0].(map[string]interface{})["text"].(string) != "failed to get resource, missing argument apiVersion" {
|
||||
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(map[string]interface{})["text"].(string))
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("resources_get with missing kind returns error", func(t *testing.T) {
|
||||
toolResult, _ := c.callTool("resources_get", map[string]interface{}{"apiVersion": "v1"})
|
||||
if !toolResult.IsError {
|
||||
t.Fatalf("call tool should fail")
|
||||
return
|
||||
}
|
||||
if toolResult.Content[0].(map[string]interface{})["text"].(string) != "failed to get resource, missing argument kind" {
|
||||
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(map[string]interface{})["text"].(string))
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("resources_get with invalid apiVersion returns error", func(t *testing.T) {
|
||||
toolResult, _ := c.callTool("resources_get", map[string]interface{}{"apiVersion": "invalid/api/version", "kind": "Pod", "name": "a-pod"})
|
||||
if !toolResult.IsError {
|
||||
t.Fatalf("call tool should fail")
|
||||
return
|
||||
}
|
||||
if toolResult.Content[0].(map[string]interface{})["text"].(string) != "failed to get resource, invalid argument apiVersion" {
|
||||
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(map[string]interface{})["text"].(string))
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("resources_get with nonexistent apiVersion returns error", func(t *testing.T) {
|
||||
toolResult, _ := c.callTool("resources_get", map[string]interface{}{"apiVersion": "custom.non.existent.example.com/v1", "kind": "Custom", "name": "a-custom"})
|
||||
if !toolResult.IsError {
|
||||
t.Fatalf("call tool should fail")
|
||||
return
|
||||
}
|
||||
if toolResult.Content[0].(map[string]interface{})["text"].(string) != `failed to get resource: no matches for kind "Custom" in version "custom.non.existent.example.com/v1"` {
|
||||
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(map[string]interface{})["text"].(string))
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("resources_get with missing name returns error", func(t *testing.T) {
|
||||
toolResult, _ := c.callTool("resources_get", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace"})
|
||||
if !toolResult.IsError {
|
||||
t.Fatalf("call tool should fail")
|
||||
return
|
||||
}
|
||||
if toolResult.Content[0].(map[string]interface{})["text"].(string) != "failed to get resource, missing argument name" {
|
||||
t.Fatalf("invalid error message, got %v", toolResult.Content[0].(map[string]interface{})["text"].(string))
|
||||
return
|
||||
}
|
||||
})
|
||||
namespace, err := c.callTool("resources_get", map[string]interface{}{"apiVersion": "v1", "kind": "Namespace", "name": "default"})
|
||||
t.Run("resources_get returns namespace", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("call tool failed %v", err)
|
||||
return
|
||||
}
|
||||
if namespace.IsError {
|
||||
t.Fatalf("call tool failed")
|
||||
return
|
||||
}
|
||||
})
|
||||
var decodedNamespace unstructured.Unstructured
|
||||
err = yaml.Unmarshal([]byte(namespace.Content[0].(map[string]interface{})["text"].(string)), &decodedNamespace)
|
||||
t.Run("resources_get has yaml content", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("invalid tool result content %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("resources_get returns default namespace", func(t *testing.T) {
|
||||
if decodedNamespace.GetName() != "default" {
|
||||
t.Fatalf("invalid namespace name, expected default, got %v", decodedNamespace.GetName())
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestResourcesCreateOrUpdate(t *testing.T) {
|
||||
testCase(t, func(c *mcpContext) {
|
||||
c.withEnvTest()
|
||||
|
||||
Reference in New Issue
Block a user