Skip to content

Commit 0398af6

Browse files
committed
Add OLMv1 wrapper, MCP tools, upgrade & tests
Signed-off-by: Brett Tofel <btofel@redhat.com>
1 parent 7a3d668 commit 0398af6

File tree

7 files changed

+530
-0
lines changed

7 files changed

+530
-0
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ python/build/
2727
python/dist/
2828
python/kubernetes_mcp_server.egg-info/
2929
!python/kubernetes-mcp-server
30+
/kubernetes-mcp-server.iml

pkg/kubernetes/kubernetes.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/containers/kubernetes-mcp-server/pkg/config"
2424
"github.com/containers/kubernetes-mcp-server/pkg/helm"
25+
"github.com/containers/kubernetes-mcp-server/pkg/olm"
2526

2627
_ "k8s.io/client-go/plugin/pkg/client/auth/oidc"
2728
)
@@ -212,3 +213,8 @@ func (k *Kubernetes) NewHelm() *helm.Helm {
212213
// This is a derived Kubernetes, so it already has the Helm initialized
213214
return helm.NewHelm(k.manager)
214215
}
216+
217+
func (k *Kubernetes) NewOlm() *olm.Olm {
218+
// Provide an OLM wrapper backed by the manager
219+
return olm.NewOlm(k.manager)
220+
}

pkg/mcp/olm.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
package mcp
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/mark3labs/mcp-go/mcp"
8+
"github.com/mark3labs/mcp-go/server"
9+
)
10+
11+
func (s *Server) initOlm() []server.ServerTool {
12+
return []server.ServerTool{
13+
{Tool: mcp.NewTool("olm_install",
14+
mcp.WithDescription("Install an OLMv1 ClusterExtension resource from a manifest (YAML or JSON)"),
15+
mcp.WithString("manifest", mcp.Description("ClusterExtension manifest to create or update (YAML or JSON)"), mcp.Required()),
16+
// Tool annotations
17+
mcp.WithTitleAnnotation("OLM: Install"),
18+
mcp.WithReadOnlyHintAnnotation(false),
19+
mcp.WithDestructiveHintAnnotation(false),
20+
mcp.WithIdempotentHintAnnotation(false),
21+
mcp.WithOpenWorldHintAnnotation(true),
22+
), Handler: s.olmInstall},
23+
{Tool: mcp.NewTool("olm_list",
24+
mcp.WithDescription("List OLMv1 ClusterExtension resources in the cluster"),
25+
// Tool annotations
26+
mcp.WithTitleAnnotation("OLM: List"),
27+
mcp.WithReadOnlyHintAnnotation(true),
28+
mcp.WithDestructiveHintAnnotation(false),
29+
mcp.WithOpenWorldHintAnnotation(true),
30+
), Handler: s.olmList},
31+
{Tool: mcp.NewTool("olm_uninstall",
32+
mcp.WithDescription("Uninstall (delete) an OLMv1 ClusterExtension resource by name"),
33+
mcp.WithString("name", mcp.Description("Name of the ClusterExtension to delete"), mcp.Required()),
34+
// Tool annotations
35+
mcp.WithTitleAnnotation("OLM: Uninstall"),
36+
mcp.WithReadOnlyHintAnnotation(false),
37+
mcp.WithDestructiveHintAnnotation(true),
38+
mcp.WithIdempotentHintAnnotation(true),
39+
mcp.WithOpenWorldHintAnnotation(true),
40+
), Handler: s.olmUninstall},
41+
{Tool: mcp.NewTool("olm_upgrade",
42+
mcp.WithDescription("Upgrade (update) an existing OLMv1 ClusterExtension resource by name using a manifest"),
43+
mcp.WithString("name", mcp.Description("Name of the ClusterExtension to upgrade"), mcp.Required()),
44+
mcp.WithString("manifest", mcp.Description("Manifest to apply to the ClusterExtension (YAML or JSON)"), mcp.Required()),
45+
// Tool annotations
46+
mcp.WithTitleAnnotation("OLM: Upgrade"),
47+
mcp.WithReadOnlyHintAnnotation(false),
48+
mcp.WithDestructiveHintAnnotation(false),
49+
mcp.WithIdempotentHintAnnotation(true),
50+
mcp.WithOpenWorldHintAnnotation(true),
51+
), Handler: s.olmUpgrade},
52+
}
53+
}
54+
55+
func (s *Server) olmInstall(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
56+
manifest, ok := ctr.GetArguments()["manifest"].(string)
57+
if !ok || manifest == "" {
58+
return NewTextResult("", fmt.Errorf("missing argument manifest")), nil
59+
}
60+
derived, err := s.k.Derived(ctx)
61+
if err != nil {
62+
return nil, err
63+
}
64+
ret, err := derived.NewOlm().Install(ctx, manifest)
65+
if err != nil {
66+
return NewTextResult("", fmt.Errorf("failed to install ClusterExtension: %w", err)), nil
67+
}
68+
return NewTextResult(ret, nil), nil
69+
}
70+
71+
func (s *Server) olmList(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
72+
derived, err := s.k.Derived(ctx)
73+
if err != nil {
74+
return nil, err
75+
}
76+
ret, err := derived.NewOlm().List(ctx)
77+
if err != nil {
78+
return NewTextResult("", fmt.Errorf("failed to list ClusterExtensions: %w", err)), nil
79+
}
80+
return NewTextResult(ret, nil), nil
81+
}
82+
83+
func (s *Server) olmUninstall(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
84+
name, ok := ctr.GetArguments()["name"].(string)
85+
if !ok || name == "" {
86+
return NewTextResult("", fmt.Errorf("missing argument name")), nil
87+
}
88+
derived, err := s.k.Derived(ctx)
89+
if err != nil {
90+
return nil, err
91+
}
92+
ret, err := derived.NewOlm().Uninstall(ctx, name)
93+
if err != nil {
94+
return NewTextResult("", fmt.Errorf("failed to uninstall ClusterExtension: %w", err)), nil
95+
}
96+
return NewTextResult(ret, nil), nil
97+
}
98+
99+
func (s *Server) olmUpgrade(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
100+
name, ok := ctr.GetArguments()["name"].(string)
101+
if !ok || name == "" {
102+
return NewTextResult("", fmt.Errorf("missing argument name")), nil
103+
}
104+
manifest, ok := ctr.GetArguments()["manifest"].(string)
105+
if !ok || manifest == "" {
106+
return NewTextResult("", fmt.Errorf("missing argument manifest")), nil
107+
}
108+
derived, err := s.k.Derived(ctx)
109+
if err != nil {
110+
return nil, err
111+
}
112+
ret, err := derived.NewOlm().Upgrade(ctx, name, manifest)
113+
if err != nil {
114+
return NewTextResult("", fmt.Errorf("failed to upgrade ClusterExtension: %w", err)), nil
115+
}
116+
return NewTextResult(ret, nil), nil
117+
}

pkg/mcp/olm_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package mcp
2+
3+
import (
4+
"testing"
5+
)
6+
7+
func TestInitOlmTools(t *testing.T) {
8+
s := &Server{}
9+
tools := s.initOlm()
10+
if len(tools) != 4 {
11+
t.Fatalf("expected 4 tools, got %d", len(tools))
12+
}
13+
names := map[string]bool{}
14+
for _, t := range tools {
15+
names[t.Tool.Name] = true
16+
}
17+
if !names["olm_install"] || !names["olm_list"] || !names["olm_uninstall"] || !names["olm_upgrade"] {
18+
t.Fatalf("missing expected olm tool names: %v", names)
19+
}
20+
}

pkg/mcp/profiles.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ func (p *FullProfile) GetTools(s *Server) []server.ServerTool {
4343
s.initPods(),
4444
s.initResources(),
4545
s.initHelm(),
46+
s.initOlm(),
4647
)
4748
}
4849

0 commit comments

Comments
 (0)