mirror of
https://github.com/containers/kubernetes-mcp-server.git
synced 2025-10-23 01:22:57 +03:00
feat(output): configurable output architecture
This commit is contained in:
@@ -5,6 +5,7 @@ import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/mcp"
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/output"
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/version"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
"github.com/spf13/cobra"
|
||||
@@ -46,8 +47,14 @@ Kubernetes Model Context Protocol (MCP) server
|
||||
fmt.Printf("Invalid profile name: %s, valid names are: %s\n", viper.GetString("profile"), strings.Join(mcp.ProfileNames, ", "))
|
||||
os.Exit(1)
|
||||
}
|
||||
o := output.FromString(viper.GetString("output"))
|
||||
if o == nil {
|
||||
fmt.Printf("Invalid output name: %s, valid names are: %s\n", viper.GetString("output"), strings.Join(output.Names, ", "))
|
||||
os.Exit(1)
|
||||
}
|
||||
klog.V(1).Info("Starting kubernetes-mcp-server")
|
||||
klog.V(1).Infof(" - Profile: %s", profile.GetName())
|
||||
klog.V(1).Infof(" - Output: %s", o.GetName())
|
||||
klog.V(1).Infof(" - Read-only mode: %t", viper.GetBool("read-only"))
|
||||
klog.V(1).Infof(" - Disable destructive tools: %t", viper.GetBool("disable-destructive"))
|
||||
if viper.GetBool("version") {
|
||||
@@ -56,6 +63,7 @@ Kubernetes Model Context Protocol (MCP) server
|
||||
}
|
||||
mcpServer, err := mcp.NewSever(mcp.Configuration{
|
||||
Profile: profile,
|
||||
Output: o,
|
||||
ReadOnly: viper.GetBool("read-only"),
|
||||
DisableDestructive: viper.GetBool("disable-destructive"),
|
||||
Kubeconfig: viper.GetString("kubeconfig"),
|
||||
@@ -121,14 +129,21 @@ func (p *profileFlag) Type() string {
|
||||
return "profile"
|
||||
}
|
||||
|
||||
func init() {
|
||||
// flagInit initializes the flags for the root command.
|
||||
// Exposed for testing purposes.
|
||||
func flagInit() {
|
||||
rootCmd.Flags().BoolP("version", "v", false, "Print version information and quit")
|
||||
rootCmd.Flags().IntP("log-level", "", 0, "Set the log level (from 0 to 9)")
|
||||
rootCmd.Flags().IntP("sse-port", "", 0, "Start a SSE server on the specified port")
|
||||
rootCmd.Flags().StringP("sse-base-url", "", "", "SSE public base URL to use when sending the endpoint message (e.g. https://example.com)")
|
||||
rootCmd.Flags().StringP("kubeconfig", "", "", "Path to the kubeconfig file to use for authentication")
|
||||
rootCmd.Flags().String("profile", "full", "MCP profile to use (one of: "+strings.Join(mcp.ProfileNames, ", ")+")")
|
||||
rootCmd.Flags().String("profile", "full", "MCP profile to use (one of: "+strings.Join(mcp.ProfileNames, ", ")+") default is full")
|
||||
rootCmd.Flags().String("output", "yaml", "Output format for resources (one of: "+strings.Join(output.Names, ", ")+") default is yaml")
|
||||
rootCmd.Flags().Bool("read-only", false, "If true, only tools annotated with readOnlyHint=true are exposed")
|
||||
rootCmd.Flags().Bool("disable-destructive", false, "If true, tools annotated with destructiveHint=true are disabled")
|
||||
_ = viper.BindPFlags(rootCmd.Flags())
|
||||
}
|
||||
|
||||
func init() {
|
||||
flagInit()
|
||||
}
|
||||
|
||||
@@ -22,22 +22,51 @@ func captureOutput(f func() error) (string, error) {
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
rootCmd.SetArgs([]string{"--version"})
|
||||
rootCmd.ResetFlags()
|
||||
flagInit()
|
||||
version, err := captureOutput(rootCmd.Execute)
|
||||
if version != "0.0.0\n" {
|
||||
t.Fatalf("Expected version 0.0.0, got %s %v", version, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultProfile(t *testing.T) {
|
||||
rootCmd.SetArgs([]string{"--version", "--log-level=1"})
|
||||
out, err := captureOutput(rootCmd.Execute)
|
||||
if !strings.Contains(out, "- Profile: full") {
|
||||
t.Fatalf("Expected profile 'full', got %s %v", out, err)
|
||||
}
|
||||
func TestProfile(t *testing.T) {
|
||||
t.Run("default", func(t *testing.T) {
|
||||
rootCmd.SetArgs([]string{"--version", "--log-level=1"})
|
||||
rootCmd.ResetFlags()
|
||||
flagInit()
|
||||
out, err := captureOutput(rootCmd.Execute)
|
||||
if !strings.Contains(out, "- Profile: full") {
|
||||
t.Fatalf("Expected profile 'full', got %s %v", out, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestOutput(t *testing.T) {
|
||||
t.Run("available", func(t *testing.T) {
|
||||
rootCmd.SetArgs([]string{"--help"})
|
||||
rootCmd.ResetFlags()
|
||||
flagInit()
|
||||
out, err := captureOutput(rootCmd.Execute)
|
||||
if !strings.Contains(out, "Output format for resources (one of: yaml)") {
|
||||
t.Fatalf("Expected all available outputs, got %s %v", out, err)
|
||||
}
|
||||
})
|
||||
t.Run("default", func(t *testing.T) {
|
||||
rootCmd.SetArgs([]string{"--version", "--log-level=1"})
|
||||
rootCmd.ResetFlags()
|
||||
flagInit()
|
||||
out, err := captureOutput(rootCmd.Execute)
|
||||
if !strings.Contains(out, "- Output: yaml") {
|
||||
t.Fatalf("Expected output 'yaml', got %s %v", out, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestDefaultReadOnly(t *testing.T) {
|
||||
rootCmd.SetArgs([]string{"--version", "--log-level=1"})
|
||||
rootCmd.ResetFlags()
|
||||
flagInit()
|
||||
out, err := captureOutput(rootCmd.Execute)
|
||||
if !strings.Contains(out, " - Read-only mode: false") {
|
||||
t.Fatalf("Expected read-only mode false, got %s %v", out, err)
|
||||
@@ -46,6 +75,8 @@ func TestDefaultReadOnly(t *testing.T) {
|
||||
|
||||
func TestDefaultDisableDestructive(t *testing.T) {
|
||||
rootCmd.SetArgs([]string{"--version", "--log-level=1"})
|
||||
rootCmd.ResetFlags()
|
||||
flagInit()
|
||||
out, err := captureOutput(rootCmd.Execute)
|
||||
if !strings.Contains(out, " - Disable destructive tools: false") {
|
||||
t.Fatalf("Expected disable destructive false, got %s %v", out, err)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package kubernetes
|
||||
|
||||
import (
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/output"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
@@ -109,5 +110,5 @@ func (k *Kubernetes) ConfigurationView(minify bool) (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return marshal(convertedObj)
|
||||
return output.MarshalYaml(convertedObj)
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package kubernetes
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/output"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
@@ -46,7 +47,7 @@ func (k *Kubernetes) EventsList(ctx context.Context, namespace string) (string,
|
||||
"Message": strings.TrimSpace(event.Message),
|
||||
})
|
||||
}
|
||||
yamlEvents, err := marshal(eventMap)
|
||||
yamlEvents, err := output.MarshalYaml(eventMap)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/helm"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/discovery/cached/memory"
|
||||
@@ -18,7 +17,6 @@ import (
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/klog/v2"
|
||||
"sigs.k8s.io/yaml"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -164,25 +162,3 @@ func (k *Kubernetes) Derived(ctx context.Context) *Kubernetes {
|
||||
derived.Helm = helm.NewHelm(derived)
|
||||
return derived
|
||||
}
|
||||
|
||||
func marshal(v any) (string, error) {
|
||||
switch t := v.(type) {
|
||||
case []unstructured.Unstructured:
|
||||
for i := range t {
|
||||
t[i].SetManagedFields(nil)
|
||||
}
|
||||
case []*unstructured.Unstructured:
|
||||
for i := range t {
|
||||
t[i].SetManagedFields(nil)
|
||||
}
|
||||
case unstructured.Unstructured:
|
||||
t.SetManagedFields(nil)
|
||||
case *unstructured.Unstructured:
|
||||
t.SetManagedFields(nil)
|
||||
}
|
||||
ret, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
@@ -2,16 +2,17 @@ package kubernetes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
func (k *Kubernetes) NamespacesList(ctx context.Context) (string, error) {
|
||||
func (k *Kubernetes) NamespacesList(ctx context.Context) ([]unstructured.Unstructured, error) {
|
||||
return k.ResourcesList(ctx, &schema.GroupVersionKind{
|
||||
Group: "", Version: "v1", Kind: "Namespace",
|
||||
}, "")
|
||||
}
|
||||
|
||||
func (k *Kubernetes) ProjectsList(ctx context.Context) (string, error) {
|
||||
func (k *Kubernetes) ProjectsList(ctx context.Context) ([]unstructured.Unstructured, error) {
|
||||
return k.ResourcesList(ctx, &schema.GroupVersionKind{
|
||||
Group: "project.openshift.io", Version: "v1", Kind: "Project",
|
||||
}, "")
|
||||
|
||||
@@ -18,19 +18,19 @@ import (
|
||||
"k8s.io/client-go/tools/remotecommand"
|
||||
)
|
||||
|
||||
func (k *Kubernetes) PodsListInAllNamespaces(ctx context.Context, labelSelector string) (string, error) {
|
||||
func (k *Kubernetes) PodsListInAllNamespaces(ctx context.Context, labelSelector string) ([]unstructured.Unstructured, error) {
|
||||
return k.ResourcesList(ctx, &schema.GroupVersionKind{
|
||||
Group: "", Version: "v1", Kind: "Pod",
|
||||
}, "", labelSelector)
|
||||
}
|
||||
|
||||
func (k *Kubernetes) PodsListInNamespace(ctx context.Context, namespace string, labelSelector string) (string, error) {
|
||||
func (k *Kubernetes) PodsListInNamespace(ctx context.Context, namespace string, labelSelector string) ([]unstructured.Unstructured, error) {
|
||||
return k.ResourcesList(ctx, &schema.GroupVersionKind{
|
||||
Group: "", Version: "v1", Kind: "Pod",
|
||||
}, namespace, labelSelector)
|
||||
}
|
||||
|
||||
func (k *Kubernetes) PodsGet(ctx context.Context, namespace, name string) (string, error) {
|
||||
func (k *Kubernetes) PodsGet(ctx context.Context, namespace, name string) (*unstructured.Unstructured, error) {
|
||||
return k.ResourcesGet(ctx, &schema.GroupVersionKind{
|
||||
Group: "", Version: "v1", Kind: "Pod",
|
||||
}, k.NamespaceOrDefault(namespace), name)
|
||||
|
||||
@@ -2,6 +2,7 @@ package kubernetes
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/output"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -20,32 +21,28 @@ const (
|
||||
AppKubernetesPartOf = "app.kubernetes.io/part-of"
|
||||
)
|
||||
|
||||
func (k *Kubernetes) ResourcesList(ctx context.Context, gvk *schema.GroupVersionKind, namespace string, labelSelector ...string) (string, error) {
|
||||
func (k *Kubernetes) ResourcesList(ctx context.Context, gvk *schema.GroupVersionKind, namespace string, labelSelector ...string) ([]unstructured.Unstructured, error) {
|
||||
var selector string
|
||||
if len(labelSelector) > 0 {
|
||||
selector = labelSelector[0]
|
||||
}
|
||||
rl, err := k.resourcesList(ctx, gvk, namespace, selector)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
return marshal(rl.Items)
|
||||
return rl.Items, nil
|
||||
}
|
||||
|
||||
func (k *Kubernetes) ResourcesGet(ctx context.Context, gvk *schema.GroupVersionKind, namespace, name string) (string, error) {
|
||||
func (k *Kubernetes) ResourcesGet(ctx context.Context, gvk *schema.GroupVersionKind, namespace, name string) (*unstructured.Unstructured, error) {
|
||||
gvr, err := k.resourceFor(gvk)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
// If it's a namespaced resource and namespace wasn't provided, try to use the default configured one
|
||||
if namespaced, nsErr := k.isNamespaced(gvk); nsErr == nil && namespaced {
|
||||
namespace = k.NamespaceOrDefault(namespace)
|
||||
}
|
||||
rg, err := k.dynamicClient.Resource(*gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return marshal(rg)
|
||||
return k.dynamicClient.Resource(*gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
}
|
||||
|
||||
func (k *Kubernetes) ResourcesCreateOrUpdate(ctx context.Context, resource string) (string, error) {
|
||||
@@ -112,7 +109,7 @@ func (k *Kubernetes) resourcesCreateOrUpdate(ctx context.Context, resources []*u
|
||||
k.deferredDiscoveryRESTMapper.Reset()
|
||||
}
|
||||
}
|
||||
marshalledYaml, err := marshal(resources)
|
||||
marshalledYaml, err := output.MarshalYaml(resources)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/output"
|
||||
"github.com/mark3labs/mcp-go/client"
|
||||
"github.com/mark3labs/mcp-go/client/transport"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
@@ -96,6 +97,7 @@ func TestMain(m *testing.M) {
|
||||
|
||||
type mcpContext struct {
|
||||
profile Profile
|
||||
output output.Output
|
||||
readOnly bool
|
||||
disableDestructive bool
|
||||
clientOptions []transport.ClientOption
|
||||
@@ -117,11 +119,17 @@ func (c *mcpContext) beforeEach(t *testing.T) {
|
||||
if c.profile == nil {
|
||||
c.profile = &FullProfile{}
|
||||
}
|
||||
if c.output == nil {
|
||||
c.output = &output.YamlOutput{}
|
||||
}
|
||||
if c.before != nil {
|
||||
c.before(c)
|
||||
}
|
||||
if c.mcpServer, err = NewSever(Configuration{
|
||||
Profile: c.profile, ReadOnly: c.readOnly, DisableDestructive: c.disableDestructive,
|
||||
Profile: c.profile,
|
||||
Output: c.output,
|
||||
ReadOnly: c.readOnly,
|
||||
DisableDestructive: c.disableDestructive,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
return
|
||||
|
||||
@@ -3,6 +3,7 @@ package mcp
|
||||
import (
|
||||
"context"
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/kubernetes"
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/output"
|
||||
"github.com/manusa/kubernetes-mcp-server/pkg/version"
|
||||
"github.com/mark3labs/mcp-go/mcp"
|
||||
"github.com/mark3labs/mcp-go/server"
|
||||
@@ -11,6 +12,7 @@ import (
|
||||
|
||||
type Configuration struct {
|
||||
Profile Profile
|
||||
Output output.Output
|
||||
// When true, expose only tools annotated with readOnlyHint=true
|
||||
ReadOnly bool
|
||||
// When true, disable tools annotated with destructiveHint=true
|
||||
|
||||
@@ -37,15 +37,15 @@ func (s *Server) initNamespaces() []server.ServerTool {
|
||||
func (s *Server) namespacesList(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
ret, err := s.k.Derived(ctx).NamespacesList(ctx)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to list namespaces: %v", err)
|
||||
return NewTextResult("", fmt.Errorf("failed to list namespaces: %v", err)), nil
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
return NewTextResult(s.configuration.Output.PrintObj(ret)), nil
|
||||
}
|
||||
|
||||
func (s *Server) projectsList(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
ret, err := s.k.Derived(ctx).ProjectsList(ctx)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to list projects: %v", err)
|
||||
return NewTextResult("", fmt.Errorf("failed to list projects: %v", err)), nil
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
return NewTextResult(s.configuration.Output.PrintObj(ret)), nil
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ func (s *Server) podsListInAllNamespaces(ctx context.Context, ctr mcp.CallToolRe
|
||||
if err != nil {
|
||||
return NewTextResult("", fmt.Errorf("failed to list pods in all namespaces: %v", err)), nil
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
return NewTextResult(s.configuration.Output.PrintObj(ret)), nil
|
||||
}
|
||||
|
||||
func (s *Server) podsListInNamespace(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
@@ -131,7 +131,7 @@ func (s *Server) podsListInNamespace(ctx context.Context, ctr mcp.CallToolReques
|
||||
if err != nil {
|
||||
return NewTextResult("", fmt.Errorf("failed to list pods in namespace %s: %v", ns, err)), nil
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
return NewTextResult(s.configuration.Output.PrintObj(ret)), nil
|
||||
}
|
||||
|
||||
func (s *Server) podsGet(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
@@ -147,7 +147,7 @@ func (s *Server) podsGet(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.Cal
|
||||
if err != nil {
|
||||
return NewTextResult("", fmt.Errorf("failed to get pod %s in namespace %s: %v", name, ns, err)), nil
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
return NewTextResult(s.configuration.Output.PrintObj(ret)), nil
|
||||
}
|
||||
|
||||
func (s *Server) podsDelete(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
|
||||
@@ -21,11 +21,9 @@ func TestPodsListInAllNamespaces(t *testing.T) {
|
||||
t.Run("pods_list returns pods list", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("call tool failed %v", err)
|
||||
return
|
||||
}
|
||||
if toolResult.IsError {
|
||||
t.Fatalf("call tool failed")
|
||||
return
|
||||
}
|
||||
})
|
||||
var decoded []unstructured.Unstructured
|
||||
@@ -33,39 +31,32 @@ func TestPodsListInAllNamespaces(t *testing.T) {
|
||||
t.Run("pods_list has yaml content", func(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("invalid tool result content %v", err)
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("pods_list returns 3 items", func(t *testing.T) {
|
||||
if len(decoded) != 3 {
|
||||
t.Fatalf("invalid pods count, expected 3, got %v", len(decoded))
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("pods_list returns pod in ns-1", func(t *testing.T) {
|
||||
if decoded[1].GetName() != "a-pod-in-ns-1" {
|
||||
t.Fatalf("invalid pod name, expected a-pod-in-ns-1, got %v", decoded[1].GetName())
|
||||
return
|
||||
}
|
||||
if decoded[1].GetNamespace() != "ns-1" {
|
||||
t.Fatalf("invalid pod namespace, expected ns-1, got %v", decoded[1].GetNamespace())
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("pods_list returns pod in ns-2", func(t *testing.T) {
|
||||
if decoded[2].GetName() != "a-pod-in-ns-2" {
|
||||
t.Fatalf("invalid pod name, expected a-pod-in-ns-2, got %v", decoded[2].GetName())
|
||||
return
|
||||
}
|
||||
if decoded[2].GetNamespace() != "ns-2" {
|
||||
t.Fatalf("invalid pod namespace, expected ns-2, got %v", decoded[2].GetNamespace())
|
||||
return
|
||||
}
|
||||
})
|
||||
t.Run("pods_list omits managed fields", func(t *testing.T) {
|
||||
if decoded[1].GetManagedFields() != nil {
|
||||
t.Fatalf("managed fields should be omitted, got %v", decoded[0].GetManagedFields())
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,18 +5,18 @@ import (
|
||||
"slices"
|
||||
)
|
||||
|
||||
var Profiles = []Profile{
|
||||
&FullProfile{},
|
||||
}
|
||||
|
||||
var ProfileNames []string
|
||||
|
||||
type Profile interface {
|
||||
GetName() string
|
||||
GetDescription() string
|
||||
GetTools(s *Server) []server.ServerTool
|
||||
}
|
||||
|
||||
var Profiles = []Profile{
|
||||
&FullProfile{},
|
||||
}
|
||||
|
||||
var ProfileNames []string
|
||||
|
||||
func ProfileFromString(name string) Profile {
|
||||
for _, profile := range Profiles {
|
||||
if profile.GetName() == name {
|
||||
|
||||
@@ -115,7 +115,7 @@ func (s *Server) resourcesList(ctx context.Context, ctr mcp.CallToolRequest) (*m
|
||||
if err != nil {
|
||||
return NewTextResult("", fmt.Errorf("failed to list resources: %v", err)), nil
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
return NewTextResult(s.configuration.Output.PrintObj(ret)), nil
|
||||
}
|
||||
|
||||
func (s *Server) resourcesGet(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
@@ -135,7 +135,7 @@ func (s *Server) resourcesGet(ctx context.Context, ctr mcp.CallToolRequest) (*mc
|
||||
if err != nil {
|
||||
return NewTextResult("", fmt.Errorf("failed to get resource: %v", err)), nil
|
||||
}
|
||||
return NewTextResult(ret, err), nil
|
||||
return NewTextResult(s.configuration.Output.PrintObj(ret)), nil
|
||||
}
|
||||
|
||||
func (s *Server) resourcesCreateOrUpdate(ctx context.Context, ctr mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||
|
||||
64
pkg/output/output.go
Normal file
64
pkg/output/output.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package output
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
type Output interface {
|
||||
GetName() string
|
||||
PrintObj(obj any) (string, error)
|
||||
}
|
||||
|
||||
var Outputs = []Output{
|
||||
&YamlOutput{},
|
||||
}
|
||||
|
||||
var Names []string
|
||||
|
||||
func FromString(name string) Output {
|
||||
for _, output := range Outputs {
|
||||
if output.GetName() == name {
|
||||
return output
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type YamlOutput struct{}
|
||||
|
||||
func (p *YamlOutput) GetName() string {
|
||||
return "yaml"
|
||||
}
|
||||
func (p *YamlOutput) PrintObj(obj any) (string, error) {
|
||||
return MarshalYaml(obj)
|
||||
}
|
||||
|
||||
func MarshalYaml(v any) (string, error) {
|
||||
switch t := v.(type) {
|
||||
case []unstructured.Unstructured:
|
||||
for i := range t {
|
||||
t[i].SetManagedFields(nil)
|
||||
}
|
||||
case []*unstructured.Unstructured:
|
||||
for i := range t {
|
||||
t[i].SetManagedFields(nil)
|
||||
}
|
||||
case unstructured.Unstructured:
|
||||
t.SetManagedFields(nil)
|
||||
case *unstructured.Unstructured:
|
||||
t.SetManagedFields(nil)
|
||||
}
|
||||
ret, err := yaml.Marshal(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(ret), nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
Names = make([]string, 0)
|
||||
for _, output := range Outputs {
|
||||
Names = append(Names, output.GetName())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user