Skip to content

Commit d7075f2

Browse files
committed
feat: improved prompt efficiency for OpenShift resources
1 parent f712653 commit d7075f2

File tree

4 files changed

+67
-10
lines changed

4 files changed

+67
-10
lines changed

pkg/kubernetes/openshift.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package kubernetes
2+
3+
import (
4+
"context"
5+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
6+
"k8s.io/apimachinery/pkg/runtime/schema"
7+
)
8+
9+
func (k *Kubernetes) IsOpenShift(ctx context.Context) bool {
10+
if _, err := k.dynamicClient.Resource(schema.GroupVersionResource{
11+
Group: "project.openshift.io",
12+
Version: "v1",
13+
Resource: "projects",
14+
}).List(ctx, metav1.ListOptions{Limit: 1}); err == nil {
15+
return true
16+
}
17+
return false
18+
}

pkg/mcp/common_test.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,21 +174,29 @@ func (c *mcpContext) withEnvTest() {
174174
// inOpenShift sets up the kubernetes environment to seem to be running OpenShift
175175
func (c *mcpContext) inOpenShift() func() {
176176
c.withKubeConfig(envTestRestConfig)
177-
return c.crdApply(`
177+
crdTemplate := `
178178
{
179179
"apiVersion": "apiextensions.k8s.io/v1",
180180
"kind": "CustomResourceDefinition",
181-
"metadata": {"name": "routes.route.openshift.io"},
181+
"metadata": {"name": "%s"},
182182
"spec": {
183-
"group": "route.openshift.io",
183+
"group": "%s",
184184
"versions": [{
185185
"name": "v1","served": true,"storage": true,
186186
"schema": {"openAPIV3Schema": {"type": "object","x-kubernetes-preserve-unknown-fields": true}}
187187
}],
188188
"scope": "Namespaced",
189-
"names": {"plural": "routes","singular": "route","kind": "Route"}
189+
"names": {"plural": "%s","singular": "%s","kind": "%s"}
190190
}
191-
}`)
191+
}`
192+
removeProjects := c.crdApply(fmt.Sprintf(crdTemplate, "projects.project.openshift.io", "project.openshift.io",
193+
"projects", "project", "Project"))
194+
removeRoutes := c.crdApply(fmt.Sprintf(crdTemplate, "routes.route.openshift.io", "route.openshift.io",
195+
"routes", "route", "Route"))
196+
return func() {
197+
removeProjects()
198+
removeRoutes()
199+
}
192200
}
193201

194202
// newKubernetesClient creates a new Kubernetes client with the envTest kubeconfig
@@ -224,7 +232,7 @@ func (c *mcpContext) crdApply(resource string) func() {
224232
}
225233
c.crdWaitUntilReady(crd.Name)
226234
return func() {
227-
err = apiExtensionsV1Client.CustomResourceDefinitions().Delete(c.ctx, "routes.route.openshift.io", metav1.DeleteOptions{})
235+
err = apiExtensionsV1Client.CustomResourceDefinitions().Delete(c.ctx, crd.Name, metav1.DeleteOptions{})
228236
if err != nil {
229237
panic(fmt.Errorf("failed to delete CRD %v", err))
230238
}

pkg/mcp/mcp_test.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package mcp
22

33
import (
44
"github.com/mark3labs/mcp-go/mcp"
5+
"strings"
56
"testing"
67
)
78

@@ -41,3 +42,28 @@ func TestTools(t *testing.T) {
4142
}
4243
})
4344
}
45+
46+
func TestToolsInOpenShift(t *testing.T) {
47+
testCase(t, func(c *mcpContext) {
48+
defer c.inOpenShift()() // n.b. two sets of parentheses to invoke the first function
49+
c.mcpServer.server.AddTools(c.mcpServer.initResources()...)
50+
tools, err := c.mcpClient.ListTools(c.ctx, mcp.ListToolsRequest{})
51+
t.Run("ListTools returns tools", func(t *testing.T) {
52+
if err != nil {
53+
t.Fatalf("call ListTools failed %v", err)
54+
return
55+
}
56+
})
57+
t.Run("ListTools has resources_list tool with OpenShift hint", func(t *testing.T) {
58+
if tools.Tools[10].Name != "resources_list" {
59+
t.Fatalf("tool resources_list not found")
60+
return
61+
}
62+
if !strings.Contains(tools.Tools[10].Description, ", route.openshift.io/v1 Route") {
63+
t.Fatalf("tool resources_list does not have OpenShift hint, got %s", tools.Tools[9].Description)
64+
return
65+
}
66+
})
67+
})
68+
69+
}

pkg/mcp/resources.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,15 @@ import (
1010
)
1111

1212
func (s *Server) initResources() []server.ServerTool {
13+
commonApiVersion := "v1 Pod, v1 Service, apps/v1 Deployment, networking.k8s.io/v1 Ingress"
14+
if s.k.IsOpenShift(context.Background()) {
15+
commonApiVersion += ", route.openshift.io/v1 Route"
16+
}
17+
commonApiVersion = fmt.Sprintf("(common apiVersion and kind include: %s)", commonApiVersion)
1318
return []server.ServerTool{
1419
{mcp.NewTool("resources_list",
1520
mcp.WithDescription("List Kubernetes resources and objects in the current cluster by providing their apiVersion and kind and optionally the namespace\n"+
16-
"(typical apiVersion and kind include: v1 Pod, v1 Service, apps/v1 Deployment, networking.k8s.io/v1 Ingress)"),
21+
commonApiVersion),
1722
mcp.WithString("apiVersion",
1823
mcp.Description("apiVersion of the resources (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)"),
1924
mcp.Required(),
@@ -26,7 +31,7 @@ func (s *Server) initResources() []server.ServerTool {
2631
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"))), s.resourcesList},
2732
{mcp.NewTool("resources_get",
2833
mcp.WithDescription("Get a Kubernetes resource in the current cluster by providing its apiVersion, kind, optionally the namespace, and its name\n"+
29-
"(typical apiVersion and kind include: v1 Pod, v1 Service, apps/v1 Deployment, networking.k8s.io/v1 Ingress)"),
34+
commonApiVersion),
3035
mcp.WithString("apiVersion",
3136
mcp.Description("apiVersion of the resource (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)"),
3237
mcp.Required(),
@@ -42,15 +47,15 @@ func (s *Server) initResources() []server.ServerTool {
4247
), s.resourcesGet},
4348
{mcp.NewTool("resources_create_or_update",
4449
mcp.WithDescription("Create or update a Kubernetes resource in the current cluster by providing a YAML or JSON representation of the resource\n"+
45-
"(typical apiVersion and kind include: v1 Pod, v1 Service, apps/v1 Deployment, networking.k8s.io/v1 Ingress)"),
50+
commonApiVersion),
4651
mcp.WithString("resource",
4752
mcp.Description("A JSON or YAML containing a representation of the Kubernetes resource. Should include top-level fields such as apiVersion,kind,metadata, and spec"),
4853
mcp.Required(),
4954
),
5055
), s.resourcesCreateOrUpdate},
5156
{mcp.NewTool("resources_delete",
5257
mcp.WithDescription("Delete a Kubernetes resource in the current cluster by providing its apiVersion, kind, optionally the namespace, and its name\n"+
53-
"(typical apiVersion and kind include: v1 Pod, v1 Service, apps/v1 Deployment, networking.k8s.io/v1 Ingress)"),
58+
commonApiVersion),
5459
mcp.WithString("apiVersion",
5560
mcp.Description("apiVersion of the resource (examples of valid apiVersion are: v1, apps/v1, networking.k8s.io/v1)"),
5661
mcp.Required(),

0 commit comments

Comments
 (0)