From 79bcfbaf0e30645d04b4bc9d2f3c0f5c99d963e9 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Mon, 3 Aug 2026 14:38:38 +0200 Subject: [PATCH 1/3] MCP Server --- cmd/mcp/mcp.go | 73 +++++++++++++++++++++++++++++++++++++++++++++ cmd/mcp/mcp_test.go | 44 +++++++++++++++++++++++++++ cmd/root.go | 3 +- go.mod | 21 +++++++++---- go.sum | 60 ++++++++++++++++++++++++++++++++----- 5 files changed, 187 insertions(+), 14 deletions(-) create mode 100644 cmd/mcp/mcp.go create mode 100644 cmd/mcp/mcp_test.go diff --git a/cmd/mcp/mcp.go b/cmd/mcp/mcp.go new file mode 100644 index 0000000..55f2473 --- /dev/null +++ b/cmd/mcp/mcp.go @@ -0,0 +1,73 @@ +package mcp + +import ( + "context" + "fmt" + + apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" + "github.com/metal-stack/api/go/metalstack/api/v2/apiv2connect" + "github.com/metal-stack/api/go/metalstack/api/v2/apiv2mcp" + "github.com/metal-stack/cli/cmd/config" + "github.com/metal-stack/v" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/redpanda-data/protoc-gen-go-mcp/pkg/runtime" + "github.com/redpanda-data/protoc-gen-go-mcp/pkg/runtime/gosdk" + + "github.com/spf13/cobra" + "google.golang.org/grpc" +) + +func NewMCPCmd(c *config.Config) *cobra.Command { + + cmd := &cobra.Command{ + Use: "mcp", + Short: "start mcp server", + RunE: func(cmd *cobra.Command, args []string) error { + raw, mcps := gosdk.NewServer("metal-stack.io mcp server", v.V.String()) + + registerApiv2Clients(c, mcps) + + if err := raw.Run(cmd.Context(), &mcp.StdioTransport{}); err != nil { + fmt.Printf("Server error: %v\n", err) + } + + return nil + }, + } + return cmd +} + +func registerApiv2Clients(c *config.Config, mcps runtime.MCPServer) { + + // Forward to MCP Clients + apiv2mcp.ForwardToAuditServiceClient(mcps, &auditServiceGRPCAdapter{client: c.Client.Apiv2().Audit()}) + // apiv2mcp.ForwardToFilesystemServiceClient(mcps, filesystemService) + // apiv2mcp.ForwardToHealthServiceClient(mcps, healthService) + // apiv2mcp.ForwardToImageServiceClient(mcps, imageService) + // apiv2mcp.ForwardToIPServiceClient(mcps, ipService) + // apiv2mcp.ForwardToMachineServiceClient(mcps, machineService) + // apiv2mcp.ForwardToMethodServiceClient(mcps, methodService) + // apiv2mcp.ForwardToNetworkServiceClient(mcps, networkService) + // apiv2mcp.ForwardToPartitionServiceClient(mcps, partitionService) + // apiv2mcp.ForwardToProjectServiceClient(mcps, projectService) + // apiv2mcp.ForwardToSizeImageConstraintServiceClient(mcps, sizeImageConstraintService) + // apiv2mcp.ForwardToSizeReservationServiceClient(mcps, sizeReservationService) + // apiv2mcp.ForwardToSizeServiceClient(mcps, sizeService) + // apiv2mcp.ForwardToTenantServiceClient(mcps, tenantService) + // apiv2mcp.ForwardToTokenServiceClient(mcps, tokenService) + // apiv2mcp.ForwardToUserServiceClient(mcps, userService) + // apiv2mcp.ForwardToVersionServiceClient(mcps, versionService) +} + +// Created a PR to fix this in the upstream grpc-mcp compiler: https://github.com/redpanda-data/protoc-gen-go-mcp/pull/51 +type auditServiceGRPCAdapter struct { + client apiv2connect.AuditServiceClient +} + +func (a *auditServiceGRPCAdapter) Get(ctx context.Context, req *apiv2.AuditServiceGetRequest, _ ...grpc.CallOption) (*apiv2.AuditServiceGetResponse, error) { + return a.client.Get(ctx, req) +} + +func (a *auditServiceGRPCAdapter) List(ctx context.Context, req *apiv2.AuditServiceListRequest, _ ...grpc.CallOption) (*apiv2.AuditServiceListResponse, error) { + return a.client.List(ctx, req) +} diff --git a/cmd/mcp/mcp_test.go b/cmd/mcp/mcp_test.go new file mode 100644 index 0000000..777c089 --- /dev/null +++ b/cmd/mcp/mcp_test.go @@ -0,0 +1,44 @@ +package mcp + +import ( + "log" + "os/exec" + "testing" + + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/stretchr/testify/require" +) + +func TestMCPServer(t *testing.T) { + ctx := t.Context() + // Create a new client, with no features. + client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "v1.0.0"}, nil) + + // Connect to a server over stdin/stdout. + transport := &mcp.CommandTransport{Command: exec.Command("/home/stefan/bin/metalctlv2", "mcp")} + session, err := client.Connect(ctx, transport, nil) + require.NoError(t, err) + defer session.Close() + + tools, err := session.ListTools(ctx, &mcp.ListToolsParams{}) + require.NoError(t, err) + for _, tool := range tools.Tools { + t.Logf("%s", tool.Name) + t.Logf("%#v", tool) + } + + // Call a tool on the server. + params := &mcp.CallToolParams{ + Name: "metalstack_api_v2_AuditService_Get", + Arguments: map[string]any{"name": "you"}, + } + res, err := session.CallTool(ctx, params) + require.NoError(t, err) + if res.IsError { + t.Logf("tool failed %v", res.Content) + t.Fail() + } + for _, c := range res.Content { + log.Print(c.(*mcp.TextContent).Text) + } +} diff --git a/cmd/root.go b/cmd/root.go index 8041aae..a14a0a2 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -9,6 +9,7 @@ import ( adminv2 "github.com/metal-stack/cli/cmd/admin/v2" apiv2 "github.com/metal-stack/cli/cmd/api/v2" + "github.com/metal-stack/cli/cmd/mcp" "github.com/metal-stack/cli/cmd/completion" "github.com/metal-stack/cli/cmd/config" @@ -95,7 +96,7 @@ func NewRootCmd(c *config.Config) *cobra.Command { }, } - rootCmd.AddCommand(newContextCmd(c), markdownCmd, newLoginCmd(c), newLogoutCmd(c)) + rootCmd.AddCommand(newContextCmd(c), markdownCmd, newLoginCmd(c), newLogoutCmd(c), mcp.NewMCPCmd(c)) adminv2.AddCmds(rootCmd, c) apiv2.AddCmds(rootCmd, c) diff --git a/go.mod b/go.mod index 75251b0..b312058 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/metal-stack/cli -go 1.26.0 +go 1.26.1 require ( connectrpc.com/connect v1.20.0 @@ -9,20 +9,23 @@ require ( github.com/fatih/color v1.19.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/metal-stack/api v0.4.4 + github.com/metal-stack/api v0.4.5-0.20260730094648-7143fa03cce6 github.com/metal-stack/metal-lib v0.26.1 github.com/metal-stack/v v1.0.3 + github.com/modelcontextprotocol/go-sdk v1.7.0 + github.com/redpanda-data/protoc-gen-go-mcp v0.0.0-20260729122341-6334690a62b6 github.com/spf13/afero v1.15.0 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 - google.golang.org/grpc v1.82.1 + google.golang.org/grpc v1.83.0 google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af sigs.k8s.io/yaml v1.6.0 ) require ( buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260709200747-435963d16310.1 // indirect + buf.build/gen/go/redpandadata/common/protocolbuffers/go v1.36.11-20260323171043-6e06f84ad823.1 // indirect buf.build/go/protovalidate v1.2.0 // indirect buf.build/go/protoyaml v0.7.0 // indirect cel.dev/expr v0.25.2 // indirect @@ -41,6 +44,7 @@ require ( github.com/goccy/go-yaml v1.19.2 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/google/cel-go v0.30.0 // indirect + github.com/google/jsonschema-go v0.4.3 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/klauspost/compress v1.19.1 // indirect github.com/klauspost/connect-compress/v2 v2.1.1 // indirect @@ -55,20 +59,27 @@ require ( github.com/olekukonko/tablewriter v1.1.4 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/redpanda-data/common-go/api v0.0.0-20260729170702-f779fe0995dc // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect + github.com/segmentio/asm v1.2.1 // indirect + github.com/segmentio/encoding v0.5.4 // indirect github.com/spf13/cast v1.10.0 // indirect github.com/spf13/pflag v1.0.10 // indirect github.com/stretchr/objx v0.5.3 // indirect github.com/subosito/gotenv v1.6.0 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect go.yaml.in/yaml/v2 v2.4.4 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/exp v0.0.0-20260727155853-b88d891fe743 // indirect golang.org/x/net v0.57.0 // indirect + golang.org/x/oauth2 v0.36.0 // indirect + golang.org/x/sync v0.22.0 // indirect golang.org/x/sys v0.47.0 // indirect golang.org/x/text v0.40.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260727163830-6c54dddc4772 // indirect + golang.org/x/time v0.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260729162451-8efbd57d26e0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apimachinery v0.36.3 // indirect diff --git a/go.sum b/go.sum index b2fceca..5b3dce0 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260709200747-435963d16310.1 h1:fXh8CsdNpjRr8R5vFdqtIxPt/Lno2IIJlYOdZBIZn0w= buf.build/gen/go/bufbuild/protovalidate/protocolbuffers/go v1.36.11-20260709200747-435963d16310.1/go.mod h1:tvtbpgaVXZX4g6Pn+AnzFycuRK3MOz5HJfEGeEllXYM= +buf.build/gen/go/redpandadata/common/protocolbuffers/go v1.36.11-20260323171043-6e06f84ad823.1 h1:AZVH8d9Xh0ar1BbxDiY+2urib65GfAcT8rPZfM++N/Q= +buf.build/gen/go/redpandadata/common/protocolbuffers/go v1.36.11-20260323171043-6e06f84ad823.1/go.mod h1:3w7EzexwlL6PIFGbbeKZ0yHfUlAmI0aBVzF/QoFb8Cg= buf.build/go/protovalidate v1.2.0 h1:DQVrUWkmGTBij+kOYv/x2LLxwcLaGKMdzShj1/6/3H0= buf.build/go/protovalidate v1.2.0/go.mod h1:7rYiQEhqvAipoazpVNBBH2S2f8bjG4huMVy1V2Yofn4= buf.build/go/protoyaml v0.7.0 h1:z4oVoFicbpPefhT7WAykxUdfp0yEQlhMQ2mCZOY5V38= @@ -33,6 +35,10 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM= @@ -55,6 +61,8 @@ github.com/google/cel-go v0.30.0 h1:ll54AkzKunWkBn9wSoiUXbFZXYZTkdJGNXTBXUoolGo= github.com/google/cel-go v0.30.0/go.mod h1:X0bD6iVNR8pkROSOoHVdgTkzmRcosof7WQqCD6wcMc8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+DQPd0= +github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -73,14 +81,16 @@ github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsRe github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0= github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= -github.com/metal-stack/api v0.4.4 h1:NwJKCFHnbmsU7DjHLtBmpdPuPniXc2wDabHjdpXk3qA= -github.com/metal-stack/api v0.4.4/go.mod h1:E7f2GkKNSr4vBxhQwWrr/Mjnf4ctPqCX2dpDtXBqkBk= +github.com/metal-stack/api v0.4.5-0.20260730094648-7143fa03cce6 h1:Oee95Rrm1QkghwnVUCfXuJfBeUoX/BIciRB98vRZe1o= +github.com/metal-stack/api v0.4.5-0.20260730094648-7143fa03cce6/go.mod h1:ZbRkorVh9W6Pv6v8mcBo9YyYIEry1vN9II1YgEHmavc= github.com/metal-stack/metal-lib v0.26.1 h1:wMrhENm/HPq0t7cjCkhzepGD6dyxmX/4IMsaJZea874= github.com/metal-stack/metal-lib v0.26.1/go.mod h1:tnx4MM5oml10EMN6Nq6oFSbjOKB9B27WUZQUyZ4MywA= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= github.com/metal-stack/v v1.0.3/go.mod h1:YTahEu7/ishwpYKnp/VaW/7nf8+PInogkfGwLcGPdXg= github.com/minio/minlz v1.2.0 h1:6IOBuiHg04QxvbFfgFLT/9sMaO/UhL7S+ApW1mK8q5A= github.com/minio/minlz v1.2.0/go.mod h1:Ls9H7nlkASeCcdl5thjVD5Eraj6z+zGa7xtq57jIKD4= +github.com/modelcontextprotocol/go-sdk v1.7.0 h1:yqjY2dsbKAC0LSuWZVBMrHgiG8ukXv6NRo0JiALay44= +github.com/modelcontextprotocol/go-sdk v1.7.0/go.mod h1:dL7u98E/zjJTGzEq+j30jQ8K2k1mb6LeAH4inEcSGts= github.com/oklog/ulid/v2 v2.1.2 h1:IEclFb9JNvzYA6MW2SCxbLzcHTVsfqm3PrqGQJH5zec= github.com/oklog/ulid/v2 v2.1.2/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/olekukonko/cat v0.0.0-20250911104152-50322a0618f6 h1:zrbMGy9YXpIeTnGj4EljqMiZsIcE09mmF8XsD5AYOJc= @@ -91,11 +101,17 @@ github.com/olekukonko/ll v0.1.8 h1:ysHCJRGHYKzmBSdz9w5AySztx7lG8SQY+naTGYUbsz8= github.com/olekukonko/ll v0.1.8/go.mod h1:RPRC6UcscfFZgjo1nulkfMH5IM0QAYim0LfnMvUuozw= github.com/olekukonko/tablewriter v1.1.4 h1:ORUMI3dXbMnRlRggJX3+q7OzQFDdvgbN9nVWj1drm6I= github.com/olekukonko/tablewriter v1.1.4/go.mod h1:+kedxuyTtgoZLwif3P1Em4hARJs+mVnzKxmsCL/C5RY= +github.com/onsi/gomega v1.37.0 h1:CdEG8g0S133B4OswTDC/5XPSzE1OeP29QOioj2PID2Y= +github.com/onsi/gomega v1.37.0/go.mod h1:8D9+Txp43QWKhM24yyOBEdpkzN8FvJyAwecBgsU4KU0= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY= github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redpanda-data/common-go/api v0.0.0-20260729170702-f779fe0995dc h1:zFNPQRf0bsBCPiTiu0B9wUQEi1JZdQ+SZjwYVVkXldI= +github.com/redpanda-data/common-go/api v0.0.0-20260729170702-f779fe0995dc/go.mod h1:HKaTWcGjArqgy2DdN/P9KHqhWVSOVEJTqt1aGE5xx2M= +github.com/redpanda-data/protoc-gen-go-mcp v0.0.0-20260729122341-6334690a62b6 h1:ThmYtRYkopbcf1vhKlsE6OEMQWHl1VrIxk9ZgLqJpgw= +github.com/redpanda-data/protoc-gen-go-mcp v0.0.0-20260729122341-6334690a62b6/go.mod h1:qCv9zvs6wyK9+EyY0auYApUdOEgi6TVM6aWQ7Byahuc= github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= github.com/rodaine/protogofakeit v0.1.1/go.mod h1:pXn/AstBYMaSfc1/RqH3N82pBuxtWgejz1AlYpY1mI0= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -104,6 +120,10 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4= github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= +github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= @@ -121,6 +141,20 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= @@ -130,16 +164,26 @@ golang.org/x/exp v0.0.0-20260727155853-b88d891fe743 h1:ex206bKw+v3K0dm3andkrIF+i golang.org/x/exp v0.0.0-20260727155853-b88d891fe743/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q= golang.org/x/net v0.57.0 h1:K5+3DljvIuDG9/Jv9rvyMywYNFCQ9RSUY6OOTTkT+tE= golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= +golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= +golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= +golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/text v0.40.0 h1:Ub2Z6/xjgF1WrYQz2nuITOEegKFtiIy+rieRJ5lHZKs= golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= -google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772 h1:4namukbyF7JY83aWHQwi9J5ugNTnDReLJ9ZcpqOpRB4= -google.golang.org/genproto/googleapis/api v0.0.0-20260727163830-6c54dddc4772/go.mod h1:1brfde68Npq6+WA75c1EHWPijZEG1kMus61ygPZfn4A= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260727163830-6c54dddc4772 h1:zuslGE3FGxH0hC6veLvSLME3TZzun9MQzjYwo1CBN+k= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260727163830-6c54dddc4772/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= -google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= +golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260729162451-8efbd57d26e0 h1:ybvH/ZpOcpCrjtkb7oW/fdlzbEmRVeumw19SRQmNFKU= +google.golang.org/genproto/googleapis/api v0.0.0-20260729162451-8efbd57d26e0/go.mod h1:HJ9MpJLeDSstBkx1LILTpd5f41ADSMZcTPypw02qEGw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0 h1:mJiOtnGp0k/BcSgdu03G2NwnscCfCH+h2QKUBZr18KI= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.83.0 h1:JeNZEKJFbQxArAMl+hiytHauacDNqJUllNfmIMmpqnQ= +google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af h1:+5/Sw3GsDNlEmu7TfklWKPdQ0Ykja5VEmq2i817+jbI= google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From d4dd8c297e1c7b8e629d3540a57c83648ffcca8c Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Tue, 4 Aug 2026 08:35:45 +0200 Subject: [PATCH 2/3] Use modified mcp client adapters --- cmd/mcp/mcp.go | 69 +++++++++++++++++++++++++-------------------- cmd/mcp/mcp_test.go | 7 +++-- go.mod | 2 +- go.sum | 4 +-- 4 files changed, 46 insertions(+), 36 deletions(-) diff --git a/cmd/mcp/mcp.go b/cmd/mcp/mcp.go index 55f2473..80dd4a5 100644 --- a/cmd/mcp/mcp.go +++ b/cmd/mcp/mcp.go @@ -1,11 +1,9 @@ package mcp import ( - "context" "fmt" - apiv2 "github.com/metal-stack/api/go/metalstack/api/v2" - "github.com/metal-stack/api/go/metalstack/api/v2/apiv2connect" + "github.com/metal-stack/api/go/metalstack/admin/v2/adminv2mcp" "github.com/metal-stack/api/go/metalstack/api/v2/apiv2mcp" "github.com/metal-stack/cli/cmd/config" "github.com/metal-stack/v" @@ -14,7 +12,6 @@ import ( "github.com/redpanda-data/protoc-gen-go-mcp/pkg/runtime/gosdk" "github.com/spf13/cobra" - "google.golang.org/grpc" ) func NewMCPCmd(c *config.Config) *cobra.Command { @@ -26,6 +23,7 @@ func NewMCPCmd(c *config.Config) *cobra.Command { raw, mcps := gosdk.NewServer("metal-stack.io mcp server", v.V.String()) registerApiv2Clients(c, mcps) + registerAdminv2Clients(c, mcps) if err := raw.Run(cmd.Context(), &mcp.StdioTransport{}); err != nil { fmt.Printf("Server error: %v\n", err) @@ -40,34 +38,43 @@ func NewMCPCmd(c *config.Config) *cobra.Command { func registerApiv2Clients(c *config.Config, mcps runtime.MCPServer) { // Forward to MCP Clients - apiv2mcp.ForwardToAuditServiceClient(mcps, &auditServiceGRPCAdapter{client: c.Client.Apiv2().Audit()}) - // apiv2mcp.ForwardToFilesystemServiceClient(mcps, filesystemService) - // apiv2mcp.ForwardToHealthServiceClient(mcps, healthService) - // apiv2mcp.ForwardToImageServiceClient(mcps, imageService) - // apiv2mcp.ForwardToIPServiceClient(mcps, ipService) - // apiv2mcp.ForwardToMachineServiceClient(mcps, machineService) - // apiv2mcp.ForwardToMethodServiceClient(mcps, methodService) - // apiv2mcp.ForwardToNetworkServiceClient(mcps, networkService) - // apiv2mcp.ForwardToPartitionServiceClient(mcps, partitionService) - // apiv2mcp.ForwardToProjectServiceClient(mcps, projectService) - // apiv2mcp.ForwardToSizeImageConstraintServiceClient(mcps, sizeImageConstraintService) - // apiv2mcp.ForwardToSizeReservationServiceClient(mcps, sizeReservationService) - // apiv2mcp.ForwardToSizeServiceClient(mcps, sizeService) - // apiv2mcp.ForwardToTenantServiceClient(mcps, tenantService) - // apiv2mcp.ForwardToTokenServiceClient(mcps, tokenService) - // apiv2mcp.ForwardToUserServiceClient(mcps, userService) - // apiv2mcp.ForwardToVersionServiceClient(mcps, versionService) + apiv2mcp.ForwardToAuditServiceClient(mcps, c.Client.Apiv2().Audit()) + apiv2mcp.ForwardToFilesystemServiceClient(mcps, c.Client.Apiv2().Filesystem()) + apiv2mcp.ForwardToHealthServiceClient(mcps, c.Client.Apiv2().Health()) + apiv2mcp.ForwardToImageServiceClient(mcps, c.Client.Apiv2().Image()) + apiv2mcp.ForwardToIPServiceClient(mcps, c.Client.Apiv2().IP()) + apiv2mcp.ForwardToMachineServiceClient(mcps, c.Client.Apiv2().Machine()) + apiv2mcp.ForwardToMethodServiceClient(mcps, c.Client.Apiv2().Method()) + apiv2mcp.ForwardToNetworkServiceClient(mcps, c.Client.Apiv2().Network()) + apiv2mcp.ForwardToPartitionServiceClient(mcps, c.Client.Apiv2().Partition()) + apiv2mcp.ForwardToProjectServiceClient(mcps, c.Client.Apiv2().Project()) + apiv2mcp.ForwardToSizeImageConstraintServiceClient(mcps, c.Client.Apiv2().SizeImageConstraint()) + apiv2mcp.ForwardToSizeReservationServiceClient(mcps, c.Client.Apiv2().SizeReservation()) + apiv2mcp.ForwardToSizeServiceClient(mcps, c.Client.Apiv2().Size()) + apiv2mcp.ForwardToTenantServiceClient(mcps, c.Client.Apiv2().Tenant()) + apiv2mcp.ForwardToTokenServiceClient(mcps, c.Client.Apiv2().Token()) + apiv2mcp.ForwardToUserServiceClient(mcps, c.Client.Apiv2().User()) + apiv2mcp.ForwardToVersionServiceClient(mcps, c.Client.Apiv2().Version()) } -// Created a PR to fix this in the upstream grpc-mcp compiler: https://github.com/redpanda-data/protoc-gen-go-mcp/pull/51 -type auditServiceGRPCAdapter struct { - client apiv2connect.AuditServiceClient -} - -func (a *auditServiceGRPCAdapter) Get(ctx context.Context, req *apiv2.AuditServiceGetRequest, _ ...grpc.CallOption) (*apiv2.AuditServiceGetResponse, error) { - return a.client.Get(ctx, req) -} +func registerAdminv2Clients(c *config.Config, mcps runtime.MCPServer) { -func (a *auditServiceGRPCAdapter) List(ctx context.Context, req *apiv2.AuditServiceListRequest, _ ...grpc.CallOption) (*apiv2.AuditServiceListResponse, error) { - return a.client.List(ctx, req) + // Forwart to Admin MCP Clients + adminv2mcp.ForwardToAuditServiceClient(mcps, c.Client.Adminv2().Audit()) + adminv2mcp.ForwardToComponentServiceClient(mcps, c.Client.Adminv2().Component()) + adminv2mcp.ForwardToFilesystemServiceClient(mcps, c.Client.Adminv2().Filesystem()) + adminv2mcp.ForwardToImageServiceClient(mcps, c.Client.Adminv2().Image()) + adminv2mcp.ForwardToIPServiceClient(mcps, c.Client.Adminv2().IP()) + adminv2mcp.ForwardToMachineServiceClient(mcps, c.Client.Adminv2().Machine()) + adminv2mcp.ForwardToNetworkServiceClient(mcps, c.Client.Adminv2().Network()) + adminv2mcp.ForwardToPartitionServiceClient(mcps, c.Client.Adminv2().Partition()) + adminv2mcp.ForwardToProjectServiceClient(mcps, c.Client.Adminv2().Project()) + adminv2mcp.ForwardToSizeImageConstraintServiceClient(mcps, c.Client.Adminv2().SizeImageConstraint()) + adminv2mcp.ForwardToSizeReservationServiceClient(mcps, c.Client.Adminv2().SizeReservation()) + adminv2mcp.ForwardToSizeServiceClient(mcps, c.Client.Adminv2().Size()) + adminv2mcp.ForwardToSwitchServiceClient(mcps, c.Client.Adminv2().Switch()) + adminv2mcp.ForwardToTaskServiceClient(mcps, c.Client.Adminv2().Task()) + adminv2mcp.ForwardToTenantServiceClient(mcps, c.Client.Adminv2().Tenant()) + adminv2mcp.ForwardToTokenServiceClient(mcps, c.Client.Adminv2().Token()) + adminv2mcp.ForwardToVPNServiceClient(mcps, c.Client.Adminv2().VPN()) } diff --git a/cmd/mcp/mcp_test.go b/cmd/mcp/mcp_test.go index 777c089..0e5c5da 100644 --- a/cmd/mcp/mcp_test.go +++ b/cmd/mcp/mcp_test.go @@ -10,6 +10,7 @@ import ( ) func TestMCPServer(t *testing.T) { + t.SkipNow() ctx := t.Context() // Create a new client, with no features. client := mcp.NewClient(&mcp.Implementation{Name: "mcp-client", Version: "v1.0.0"}, nil) @@ -18,7 +19,9 @@ func TestMCPServer(t *testing.T) { transport := &mcp.CommandTransport{Command: exec.Command("/home/stefan/bin/metalctlv2", "mcp")} session, err := client.Connect(ctx, transport, nil) require.NoError(t, err) - defer session.Close() + defer func() { + _ = session.Close() + }() tools, err := session.ListTools(ctx, &mcp.ListToolsParams{}) require.NoError(t, err) @@ -36,7 +39,7 @@ func TestMCPServer(t *testing.T) { require.NoError(t, err) if res.IsError { t.Logf("tool failed %v", res.Content) - t.Fail() + // t.Fail() } for _, c := range res.Content { log.Print(c.(*mcp.TextContent).Text) diff --git a/go.mod b/go.mod index b312058..96ea83c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/fatih/color v1.19.0 github.com/google/go-cmp v0.7.0 github.com/google/uuid v1.6.0 - github.com/metal-stack/api v0.4.5-0.20260730094648-7143fa03cce6 + github.com/metal-stack/api v0.4.5-0.20260804062648-51225da9541a github.com/metal-stack/metal-lib v0.26.1 github.com/metal-stack/v v1.0.3 github.com/modelcontextprotocol/go-sdk v1.7.0 diff --git a/go.sum b/go.sum index 5b3dce0..a5a94b5 100644 --- a/go.sum +++ b/go.sum @@ -81,8 +81,8 @@ github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsRe github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= github.com/mattn/go-runewidth v0.0.27 h1:Feg/Oou5zI/wnpgDF6omIU0OokC9GxLC/WRknhVlIR0= github.com/mattn/go-runewidth v0.0.27/go.mod h1:3qAiGCV4Koz/yuveO58qUefmUTRm8r0IGEXZ9jeHp/8= -github.com/metal-stack/api v0.4.5-0.20260730094648-7143fa03cce6 h1:Oee95Rrm1QkghwnVUCfXuJfBeUoX/BIciRB98vRZe1o= -github.com/metal-stack/api v0.4.5-0.20260730094648-7143fa03cce6/go.mod h1:ZbRkorVh9W6Pv6v8mcBo9YyYIEry1vN9II1YgEHmavc= +github.com/metal-stack/api v0.4.5-0.20260804062648-51225da9541a h1:oXU+7KZ0QocX+7DLTEPPiYPED/t+/APSz83z6Io6i9c= +github.com/metal-stack/api v0.4.5-0.20260804062648-51225da9541a/go.mod h1:ZbRkorVh9W6Pv6v8mcBo9YyYIEry1vN9II1YgEHmavc= github.com/metal-stack/metal-lib v0.26.1 h1:wMrhENm/HPq0t7cjCkhzepGD6dyxmX/4IMsaJZea874= github.com/metal-stack/metal-lib v0.26.1/go.mod h1:tnx4MM5oml10EMN6Nq6oFSbjOKB9B27WUZQUyZ4MywA= github.com/metal-stack/v v1.0.3 h1:Sh2oBlnxrCUD+mVpzfC8HiqL045YWkxs0gpTvkjppqs= From c0de2135532a83c5d0e12074bdc63867f6633ce3 Mon Sep 17 00:00:00 2001 From: Stefan Majer Date: Tue, 4 Aug 2026 11:11:16 +0200 Subject: [PATCH 3/3] make it work --- cmd/mcp/mcp.go | 110 ++++++++++++++++++++++++++------------------ cmd/mcp/mcp_test.go | 6 +-- docs/metalctlv2.md | 1 + go.mod | 2 +- go.sum | 22 +-------- 5 files changed, 72 insertions(+), 69 deletions(-) diff --git a/cmd/mcp/mcp.go b/cmd/mcp/mcp.go index 80dd4a5..ba72c7d 100644 --- a/cmd/mcp/mcp.go +++ b/cmd/mcp/mcp.go @@ -14,67 +14,87 @@ import ( "github.com/spf13/cobra" ) +var long = fmt.Sprintf(` +Use %q to serve as mcp server. You must configure your coding agent to make use of the mcp server. + +Example opencode.json: + +"mcp": { + "metal": { + "type": "local", + "command": [ + %q, + "mcp" + ], + "enabled": true, + } +} + +Then login with %q and start you coding agent and ask questions like: + +- list all metal partitions +- give me all available metal sizes and images +- create a ip address, ask me questions +`, config.BinaryName, config.BinaryName, config.BinaryName) + func NewMCPCmd(c *config.Config) *cobra.Command { cmd := &cobra.Command{ Use: "mcp", Short: "start mcp server", + Long: long, RunE: func(cmd *cobra.Command, args []string) error { raw, mcps := gosdk.NewServer("metal-stack.io mcp server", v.V.String()) - registerApiv2Clients(c, mcps) - registerAdminv2Clients(c, mcps) + forwardToClients(c, mcps) if err := raw.Run(cmd.Context(), &mcp.StdioTransport{}); err != nil { - fmt.Printf("Server error: %v\n", err) + return err } - return nil }, } return cmd } -func registerApiv2Clients(c *config.Config, mcps runtime.MCPServer) { - - // Forward to MCP Clients - apiv2mcp.ForwardToAuditServiceClient(mcps, c.Client.Apiv2().Audit()) - apiv2mcp.ForwardToFilesystemServiceClient(mcps, c.Client.Apiv2().Filesystem()) - apiv2mcp.ForwardToHealthServiceClient(mcps, c.Client.Apiv2().Health()) - apiv2mcp.ForwardToImageServiceClient(mcps, c.Client.Apiv2().Image()) - apiv2mcp.ForwardToIPServiceClient(mcps, c.Client.Apiv2().IP()) - apiv2mcp.ForwardToMachineServiceClient(mcps, c.Client.Apiv2().Machine()) - apiv2mcp.ForwardToMethodServiceClient(mcps, c.Client.Apiv2().Method()) - apiv2mcp.ForwardToNetworkServiceClient(mcps, c.Client.Apiv2().Network()) - apiv2mcp.ForwardToPartitionServiceClient(mcps, c.Client.Apiv2().Partition()) - apiv2mcp.ForwardToProjectServiceClient(mcps, c.Client.Apiv2().Project()) - apiv2mcp.ForwardToSizeImageConstraintServiceClient(mcps, c.Client.Apiv2().SizeImageConstraint()) - apiv2mcp.ForwardToSizeReservationServiceClient(mcps, c.Client.Apiv2().SizeReservation()) - apiv2mcp.ForwardToSizeServiceClient(mcps, c.Client.Apiv2().Size()) - apiv2mcp.ForwardToTenantServiceClient(mcps, c.Client.Apiv2().Tenant()) - apiv2mcp.ForwardToTokenServiceClient(mcps, c.Client.Apiv2().Token()) - apiv2mcp.ForwardToUserServiceClient(mcps, c.Client.Apiv2().User()) - apiv2mcp.ForwardToVersionServiceClient(mcps, c.Client.Apiv2().Version()) -} - -func registerAdminv2Clients(c *config.Config, mcps runtime.MCPServer) { +func forwardToClients(c *config.Config, mcps runtime.MCPServer) { + // Be prepared for later opts. + var opts []runtime.Option + // API Clients + apiv2mcp.ForwardToAuditServiceClient(mcps, c.Client.Apiv2().Audit(), opts...) + apiv2mcp.ForwardToFilesystemServiceClient(mcps, c.Client.Apiv2().Filesystem(), opts...) + apiv2mcp.ForwardToHealthServiceClient(mcps, c.Client.Apiv2().Health(), opts...) + apiv2mcp.ForwardToImageServiceClient(mcps, c.Client.Apiv2().Image(), opts...) + apiv2mcp.ForwardToIPServiceClient(mcps, c.Client.Apiv2().IP(), opts...) + apiv2mcp.ForwardToMachineServiceClient(mcps, c.Client.Apiv2().Machine(), opts...) + apiv2mcp.ForwardToMethodServiceClient(mcps, c.Client.Apiv2().Method(), opts...) + apiv2mcp.ForwardToNetworkServiceClient(mcps, c.Client.Apiv2().Network(), opts...) + apiv2mcp.ForwardToPartitionServiceClient(mcps, c.Client.Apiv2().Partition(), opts...) + apiv2mcp.ForwardToProjectServiceClient(mcps, c.Client.Apiv2().Project(), opts...) + apiv2mcp.ForwardToSizeImageConstraintServiceClient(mcps, c.Client.Apiv2().SizeImageConstraint(), opts...) + apiv2mcp.ForwardToSizeReservationServiceClient(mcps, c.Client.Apiv2().SizeReservation(), opts...) + apiv2mcp.ForwardToSizeServiceClient(mcps, c.Client.Apiv2().Size(), opts...) + apiv2mcp.ForwardToTenantServiceClient(mcps, c.Client.Apiv2().Tenant(), opts...) + apiv2mcp.ForwardToTokenServiceClient(mcps, c.Client.Apiv2().Token(), opts...) + apiv2mcp.ForwardToUserServiceClient(mcps, c.Client.Apiv2().User(), opts...) + apiv2mcp.ForwardToVersionServiceClient(mcps, c.Client.Apiv2().Version(), opts...) - // Forwart to Admin MCP Clients - adminv2mcp.ForwardToAuditServiceClient(mcps, c.Client.Adminv2().Audit()) - adminv2mcp.ForwardToComponentServiceClient(mcps, c.Client.Adminv2().Component()) - adminv2mcp.ForwardToFilesystemServiceClient(mcps, c.Client.Adminv2().Filesystem()) - adminv2mcp.ForwardToImageServiceClient(mcps, c.Client.Adminv2().Image()) - adminv2mcp.ForwardToIPServiceClient(mcps, c.Client.Adminv2().IP()) - adminv2mcp.ForwardToMachineServiceClient(mcps, c.Client.Adminv2().Machine()) - adminv2mcp.ForwardToNetworkServiceClient(mcps, c.Client.Adminv2().Network()) - adminv2mcp.ForwardToPartitionServiceClient(mcps, c.Client.Adminv2().Partition()) - adminv2mcp.ForwardToProjectServiceClient(mcps, c.Client.Adminv2().Project()) - adminv2mcp.ForwardToSizeImageConstraintServiceClient(mcps, c.Client.Adminv2().SizeImageConstraint()) - adminv2mcp.ForwardToSizeReservationServiceClient(mcps, c.Client.Adminv2().SizeReservation()) - adminv2mcp.ForwardToSizeServiceClient(mcps, c.Client.Adminv2().Size()) - adminv2mcp.ForwardToSwitchServiceClient(mcps, c.Client.Adminv2().Switch()) - adminv2mcp.ForwardToTaskServiceClient(mcps, c.Client.Adminv2().Task()) - adminv2mcp.ForwardToTenantServiceClient(mcps, c.Client.Adminv2().Tenant()) - adminv2mcp.ForwardToTokenServiceClient(mcps, c.Client.Adminv2().Token()) - adminv2mcp.ForwardToVPNServiceClient(mcps, c.Client.Adminv2().VPN()) + // Admin Clients + adminv2mcp.ForwardToAuditServiceClient(mcps, c.Client.Adminv2().Audit(), opts...) + adminv2mcp.ForwardToComponentServiceClient(mcps, c.Client.Adminv2().Component(), opts...) + adminv2mcp.ForwardToFilesystemServiceClient(mcps, c.Client.Adminv2().Filesystem(), opts...) + adminv2mcp.ForwardToImageServiceClient(mcps, c.Client.Adminv2().Image(), opts...) + adminv2mcp.ForwardToIPServiceClient(mcps, c.Client.Adminv2().IP(), opts...) + adminv2mcp.ForwardToMachineServiceClient(mcps, c.Client.Adminv2().Machine(), opts...) + adminv2mcp.ForwardToNetworkServiceClient(mcps, c.Client.Adminv2().Network(), opts...) + adminv2mcp.ForwardToPartitionServiceClient(mcps, c.Client.Adminv2().Partition(), opts...) + adminv2mcp.ForwardToProjectServiceClient(mcps, c.Client.Adminv2().Project(), opts...) + adminv2mcp.ForwardToSizeImageConstraintServiceClient(mcps, c.Client.Adminv2().SizeImageConstraint(), opts...) + adminv2mcp.ForwardToSizeReservationServiceClient(mcps, c.Client.Adminv2().SizeReservation(), opts...) + adminv2mcp.ForwardToSizeServiceClient(mcps, c.Client.Adminv2().Size(), opts...) + adminv2mcp.ForwardToSwitchServiceClient(mcps, c.Client.Adminv2().Switch(), opts...) + adminv2mcp.ForwardToTaskServiceClient(mcps, c.Client.Adminv2().Task(), opts...) + adminv2mcp.ForwardToTenantServiceClient(mcps, c.Client.Adminv2().Tenant(), opts...) + adminv2mcp.ForwardToTokenServiceClient(mcps, c.Client.Adminv2().Token(), opts...) + adminv2mcp.ForwardToVPNServiceClient(mcps, c.Client.Adminv2().VPN(), opts...) } diff --git a/cmd/mcp/mcp_test.go b/cmd/mcp/mcp_test.go index 0e5c5da..144ce8e 100644 --- a/cmd/mcp/mcp_test.go +++ b/cmd/mcp/mcp_test.go @@ -32,16 +32,16 @@ func TestMCPServer(t *testing.T) { // Call a tool on the server. params := &mcp.CallToolParams{ - Name: "metalstack_api_v2_AuditService_Get", - Arguments: map[string]any{"name": "you"}, + Name: "metalstack_api_v2_PartitionService_List", + Arguments: map[string]any{}, } res, err := session.CallTool(ctx, params) require.NoError(t, err) if res.IsError { t.Logf("tool failed %v", res.Content) - // t.Fail() } for _, c := range res.Content { log.Print(c.(*mcp.TextContent).Text) } + t.Fail() } diff --git a/docs/metalctlv2.md b/docs/metalctlv2.md index b1579d5..649c399 100644 --- a/docs/metalctlv2.md +++ b/docs/metalctlv2.md @@ -28,6 +28,7 @@ cli for managing entities in metal-stack * [metalctlv2 login](metalctlv2_login.md) - login * [metalctlv2 logout](metalctlv2_logout.md) - logout * [metalctlv2 markdown](metalctlv2_markdown.md) - create markdown documentation +* [metalctlv2 mcp](metalctlv2_mcp.md) - start mcp server * [metalctlv2 network](metalctlv2_network.md) - manage network entities * [metalctlv2 partition](metalctlv2_partition.md) - manage partition entities * [metalctlv2 project](metalctlv2_project.md) - manage project entities diff --git a/go.mod b/go.mod index 96ea83c..a07459d 100644 --- a/go.mod +++ b/go.mod @@ -59,7 +59,7 @@ require ( github.com/olekukonko/tablewriter v1.1.4 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/redpanda-data/common-go/api v0.0.0-20260729170702-f779fe0995dc // indirect + github.com/redpanda-data/common-go/api v0.0.0-20260805234448-c124c21fdb6b // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect github.com/segmentio/asm v1.2.1 // indirect diff --git a/go.sum b/go.sum index a5a94b5..2419d7b 100644 --- a/go.sum +++ b/go.sum @@ -35,10 +35,6 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho= github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-openapi/errors v0.22.8 h1:oP7sW7TWc3wFFjrzzj0nI83H2qMBkNjNfSd+XRejk/I= github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= github.com/go-openapi/strfmt v0.27.0 h1:kbcTeaD9TXuXD0hhMXzuYa1sdTo6+dWGvwjW93E80IM= @@ -108,8 +104,8 @@ github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdD github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/redpanda-data/common-go/api v0.0.0-20260729170702-f779fe0995dc h1:zFNPQRf0bsBCPiTiu0B9wUQEi1JZdQ+SZjwYVVkXldI= -github.com/redpanda-data/common-go/api v0.0.0-20260729170702-f779fe0995dc/go.mod h1:HKaTWcGjArqgy2DdN/P9KHqhWVSOVEJTqt1aGE5xx2M= +github.com/redpanda-data/common-go/api v0.0.0-20260805234448-c124c21fdb6b h1:37u906bF7SsdlHuFwkDjFY/NAe6vKkMJUMdGNB9FBdY= +github.com/redpanda-data/common-go/api v0.0.0-20260805234448-c124c21fdb6b/go.mod h1:X4iTnBNz6LJ7KwyOkW/7xh+pBS8tuqFWUwECPkVDjIo= github.com/redpanda-data/protoc-gen-go-mcp v0.0.0-20260729122341-6334690a62b6 h1:ThmYtRYkopbcf1vhKlsE6OEMQWHl1VrIxk9ZgLqJpgw= github.com/redpanda-data/protoc-gen-go-mcp v0.0.0-20260729122341-6334690a62b6/go.mod h1:qCv9zvs6wyK9+EyY0auYApUdOEgi6TVM6aWQ7Byahuc= github.com/rodaine/protogofakeit v0.1.1 h1:ZKouljuRM3A+TArppfBqnH8tGZHOwM/pjvtXe9DaXH8= @@ -143,18 +139,6 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8 github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= -go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= -go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= -go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= -go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= -go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= -go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= @@ -176,8 +160,6 @@ golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= golang.org/x/tools v0.48.0 h1:3+hClM1aLL5mjMKm5ovokw9epgRXPuu2tILgismM6RE= golang.org/x/tools v0.48.0/go.mod h1:08xX0orndb/F7jJxGDicx061tyd5pcMto75YMAXr6lk= -gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= -gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/api v0.0.0-20260729162451-8efbd57d26e0 h1:ybvH/ZpOcpCrjtkb7oW/fdlzbEmRVeumw19SRQmNFKU= google.golang.org/genproto/googleapis/api v0.0.0-20260729162451-8efbd57d26e0/go.mod h1:HJ9MpJLeDSstBkx1LILTpd5f41ADSMZcTPypw02qEGw= google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0 h1:mJiOtnGp0k/BcSgdu03G2NwnscCfCH+h2QKUBZr18KI=