feat(profiles): bootstrap initial support for profiles

This commit is contained in:
Marc Nuri
2025-05-15 16:55:08 +02:00
committed by GitHub
parent 6afb60f73a
commit 1f22f5b23f
4 changed files with 103 additions and 27 deletions

View File

@@ -40,12 +40,18 @@ Kubernetes Model Context Protocol (MCP) server
# TODO: add more examples`,
Run: func(cmd *cobra.Command, args []string) {
initLogging()
klog.V(5).Infof("Starting kubernetes-mcp-server")
profile := mcp.ProfileFromString(viper.GetString("profile"))
if profile == nil {
fmt.Printf("Invalid profile name: %s, valid names are: %s\n", viper.GetString("profile"), mcp.ProfileNames)
os.Exit(1)
}
klog.V(1).Infof("Starting kubernetes-mcp-server with profile: %s", profile.GetName())
if viper.GetBool("version") {
fmt.Println(version.Version)
return
}
mcpServer, err := mcp.NewSever(mcp.Configuration{
Profile: profile,
Kubeconfig: viper.GetString("kubeconfig"),
})
if err != nil {
@@ -70,27 +76,51 @@ Kubernetes Model Context Protocol (MCP) server
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
klog.Errorf("Failed to execute command: %s", err)
os.Exit(1)
}
}
func initLogging() {
flagSet := flag.NewFlagSet("kubernetes-mcp-server", flag.ContinueOnError)
klog.InitFlags(flagSet)
loggerOptions := []textlogger.ConfigOption{textlogger.Output(os.Stdout)}
if logLevel := viper.GetInt("log-level"); logLevel >= 0 {
loggerOptions = append(loggerOptions, textlogger.Verbosity(logLevel))
_ = flagSet.Parse([]string{"--v", strconv.Itoa(logLevel)})
}
logger := textlogger.NewLogger(textlogger.NewConfig(loggerOptions...))
klog.SetLoggerWithOptions(logger)
}
type profileFlag struct {
mcp.Profile
}
func (p *profileFlag) String() string {
return p.GetName()
}
func (p *profileFlag) Set(v string) error {
p.Profile = mcp.ProfileFromString(v)
if p.Profile != nil {
return nil
}
return fmt.Errorf("invalid profile name: %s, valid names are: %s", v, mcp.ProfileNames)
}
func (p *profileFlag) Type() string {
return "profile"
}
func init() {
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().Var(&profileFlag{&mcp.FullProfile{}}, "profile", "MCP profile to use")
_ = viper.BindPFlags(rootCmd.Flags())
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
panic(err)
}
}
func initLogging() {
logger := textlogger.NewLogger(textlogger.NewConfig(textlogger.Output(os.Stdout)))
klog.SetLoggerWithOptions(logger)
flagSet := flag.NewFlagSet("kubernetes-mcp-server", flag.ContinueOnError)
klog.InitFlags(flagSet)
if logLevel := viper.GetInt("log-level"); logLevel >= 0 {
_ = flagSet.Parse([]string{"--v", strconv.Itoa(logLevel)})
}
}

View File

@@ -105,7 +105,7 @@ func (c *mcpContext) beforeEach(t *testing.T) {
c.ctx, c.cancel = context.WithCancel(context.Background())
c.tempDir = t.TempDir()
c.withKubeConfig(nil)
if c.mcpServer, err = NewSever(Configuration{}); err != nil {
if c.mcpServer, err = NewSever(Configuration{Profile: &FullProfile{}}); err != nil {
t.Fatal(err)
return
}

View File

@@ -5,10 +5,10 @@ import (
"github.com/manusa/kubernetes-mcp-server/pkg/version"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
"slices"
)
type Configuration struct {
Profile Profile
Kubeconfig string
}
@@ -43,14 +43,7 @@ func (s *Server) reloadKubernetesClient() error {
return err
}
s.k = k
s.server.SetTools(slices.Concat(
s.initConfiguration(),
s.initEvents(),
s.initNamespaces(),
s.initPods(),
s.initResources(),
s.initHelm(),
)...)
s.server.SetTools(s.configuration.Profile.GetTools(s)...)
return nil
}

53
pkg/mcp/profiles.go Normal file
View File

@@ -0,0 +1,53 @@
package mcp
import (
"github.com/mark3labs/mcp-go/server"
"slices"
)
var Profiles = []Profile{
&FullProfile{},
}
var ProfileNames []string
type Profile interface {
GetName() string
GetDescription() string
GetTools(s *Server) []server.ServerTool
}
func ProfileFromString(name string) Profile {
for _, profile := range Profiles {
if profile.GetName() == name {
return profile
}
}
return nil
}
type FullProfile struct{}
func (p *FullProfile) GetName() string {
return "full"
}
func (p *FullProfile) GetDescription() string {
return "Complete profile with all tools and extended outputs"
}
func (p *FullProfile) GetTools(s *Server) []server.ServerTool {
return slices.Concat(
s.initConfiguration(),
s.initEvents(),
s.initNamespaces(),
s.initPods(),
s.initResources(),
s.initHelm(),
)
}
func init() {
ProfileNames = make([]string, 0)
for _, profile := range Profiles {
ProfileNames = append(ProfileNames, profile.GetName())
}
}