diff --git a/pkg/kubernetes-mcp-server/cmd/root_test.go b/pkg/kubernetes-mcp-server/cmd/root_test.go index edda9182..46527075 100644 --- a/pkg/kubernetes-mcp-server/cmd/root_test.go +++ b/pkg/kubernetes-mcp-server/cmd/root_test.go @@ -74,7 +74,7 @@ func TestProfile(t *testing.T) { rootCmd := NewMCPServer(ioStreams) rootCmd.SetArgs([]string{"--help"}) o, err := captureOutput(rootCmd.Execute) // --help doesn't use logger/klog, cobra prints directly to stdout - if !strings.Contains(o, "MCP profile to use (one of: full) ") { + if !strings.Contains(o, "MCP profile to use (one of: full, full-safe) ") { t.Fatalf("Expected all available profiles, got %s %v", o, err) } }) diff --git a/pkg/mcp/configs/full-safe.toml b/pkg/mcp/configs/full-safe.toml new file mode 100644 index 00000000..c0a166d8 --- /dev/null +++ b/pkg/mcp/configs/full-safe.toml @@ -0,0 +1,16 @@ +# A list of denied Kubernetes resources in Group/Version/Kind format. +# If a resource is in this list, MCP server should deny all operations +# on that resource type across all namespaces. +[[denied_resources]] +group = "" +version = "v1" +kind = "ServiceAccount" + +[[denied_resources]] +group = "" +version = "v1" +kind = "Secret" + +[[denied_resources]] +group = "rbac.authorization.k8s.io" +version = "v1" diff --git a/pkg/mcp/configs/full.toml b/pkg/mcp/configs/full.toml new file mode 100644 index 00000000..e69de29b diff --git a/pkg/mcp/mcp.go b/pkg/mcp/mcp.go index 8d0d950e..7a57481f 100644 --- a/pkg/mcp/mcp.go +++ b/pkg/mcp/mcp.go @@ -2,13 +2,13 @@ package mcp import ( "context" - "github.com/manusa/kubernetes-mcp-server/pkg/config" "net/http" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" "k8s.io/utils/ptr" + "github.com/manusa/kubernetes-mcp-server/pkg/config" "github.com/manusa/kubernetes-mcp-server/pkg/kubernetes" "github.com/manusa/kubernetes-mcp-server/pkg/output" "github.com/manusa/kubernetes-mcp-server/pkg/version" @@ -44,6 +44,15 @@ func NewServer(configuration Configuration) (*Server, error) { server.WithLogging(), ), } + + if configuration.StaticConfig == nil { + staticConfig, err := configuration.Profile.GetDefaultConfig() + if err != nil { + return nil, err + } + configuration.StaticConfig = staticConfig + } + if err := s.reloadKubernetesClient(); err != nil { return nil, err } diff --git a/pkg/mcp/profiles.go b/pkg/mcp/profiles.go index 6c0d9741..dacc49bb 100644 --- a/pkg/mcp/profiles.go +++ b/pkg/mcp/profiles.go @@ -1,19 +1,32 @@ package mcp import ( + "embed" + "io/fs" "slices" + "github.com/BurntSushi/toml" "github.com/mark3labs/mcp-go/server" + + "github.com/manusa/kubernetes-mcp-server/pkg/config" ) +//go:embed configs/full.toml +var defaultFullConfigFile embed.FS + +//go:embed configs/full-safe.toml +var defaultFullSafeConfigFile embed.FS + type Profile interface { GetName() string GetDescription() string GetTools(s *Server) []server.ServerTool + GetDefaultConfig() (*config.StaticConfig, error) } var Profiles = []Profile{ &FullProfile{}, + &FullSafeProfile{}, } var ProfileNames []string @@ -45,6 +58,50 @@ func (p *FullProfile) GetTools(s *Server) []server.ServerTool { s.initHelm(), ) } +func (p *FullProfile) GetDefaultConfig() (*config.StaticConfig, error) { + data, err := fs.ReadFile(defaultFullConfigFile, "configs/full.toml") + if err != nil { + return nil, err + } + + var cfg *config.StaticConfig + err = toml.Unmarshal(data, &cfg) + if err != nil { + return nil, err + } + return cfg, nil +} + +type FullSafeProfile struct{} + +func (p *FullSafeProfile) GetName() string { + return "full-safe" +} +func (p *FullSafeProfile) GetDescription() string { + return "Complete profile with all tools and extended outputs" +} +func (p *FullSafeProfile) GetTools(s *Server) []server.ServerTool { + return slices.Concat( + s.initEvents(), + s.initNamespaces(), + s.initPods(), + s.initResources(), + s.initHelm(), + ) +} +func (p *FullSafeProfile) GetDefaultConfig() (*config.StaticConfig, error) { + data, err := fs.ReadFile(defaultFullSafeConfigFile, "configs/full-safe.toml") + if err != nil { + return nil, err + } + + var cfg *config.StaticConfig + err = toml.Unmarshal(data, &cfg) + if err != nil { + return nil, err + } + return cfg, nil +} func init() { ProfileNames = make([]string, 0)