diff --git a/pkg/http/http_test.go b/pkg/http/http_test.go index 566f1f8c..b481157a 100644 --- a/pkg/http/http_test.go +++ b/pkg/http/http_test.go @@ -87,7 +87,7 @@ func (c *httpContext) beforeEach(t *testing.T) { } c.StaticConfig.Port = fmt.Sprintf("%d", ln.Addr().(*net.TCPAddr).Port) mcpServer, err := mcp.NewServer(mcp.Configuration{ - Profile: mcp.Profiles[0], + Toolset: mcp.Toolsets[0], StaticConfig: c.StaticConfig, }) if err != nil { diff --git a/pkg/kubernetes-mcp-server/cmd/root.go b/pkg/kubernetes-mcp-server/cmd/root.go index b335938b..9534c247 100644 --- a/pkg/kubernetes-mcp-server/cmd/root.go +++ b/pkg/kubernetes-mcp-server/cmd/root.go @@ -57,7 +57,7 @@ type MCPServerOptions struct { HttpPort int SSEBaseUrl string Kubeconfig string - Profile string + Toolset string ListOutput string ReadOnly bool DisableDestructive bool @@ -77,7 +77,7 @@ type MCPServerOptions struct { func NewMCPServerOptions(streams genericiooptions.IOStreams) *MCPServerOptions { return &MCPServerOptions{ IOStreams: streams, - Profile: "full", + Toolset: "full", ListOutput: "table", StaticConfig: &config.StaticConfig{}, } @@ -107,7 +107,7 @@ func NewMCPServer(streams genericiooptions.IOStreams) *cobra.Command { cmd.Flags().BoolVar(&o.Version, "version", o.Version, "Print version information and quit") cmd.Flags().IntVar(&o.LogLevel, "log-level", o.LogLevel, "Set the log level (from 0 to 9)") - cmd.Flags().StringVar(&o.ConfigPath, "config", o.ConfigPath, "Path of the config file. Each profile has its set of defaults.") + cmd.Flags().StringVar(&o.ConfigPath, "config", o.ConfigPath, "Path of the config file.") cmd.Flags().IntVar(&o.SSEPort, "sse-port", o.SSEPort, "Start a SSE server on the specified port") cmd.Flag("sse-port").Deprecated = "Use --port instead" cmd.Flags().IntVar(&o.HttpPort, "http-port", o.HttpPort, "Start a streamable HTTP server on the specified port") @@ -115,7 +115,7 @@ func NewMCPServer(streams genericiooptions.IOStreams) *cobra.Command { cmd.Flags().StringVar(&o.Port, "port", o.Port, "Start a streamable HTTP and SSE HTTP server on the specified port (e.g. 8080)") cmd.Flags().StringVar(&o.SSEBaseUrl, "sse-base-url", o.SSEBaseUrl, "SSE public base URL to use when sending the endpoint message (e.g. https://example.com)") cmd.Flags().StringVar(&o.Kubeconfig, "kubeconfig", o.Kubeconfig, "Path to the kubeconfig file to use for authentication") - cmd.Flags().StringVar(&o.Profile, "profile", o.Profile, "MCP profile to use (one of: "+strings.Join(mcp.ProfileNames, ", ")+")") + cmd.Flags().StringVar(&o.Toolset, "toolset", o.Toolset, "MCP toolset to use (one of: "+strings.Join(mcp.ToolsetNames, ", ")+")") cmd.Flags().StringVar(&o.ListOutput, "list-output", o.ListOutput, "Output format for resource list operations (one of: "+strings.Join(output.Names, ", ")+"). Defaults to table.") cmd.Flags().BoolVar(&o.ReadOnly, "read-only", o.ReadOnly, "If true, only tools annotated with readOnlyHint=true are exposed") cmd.Flags().BoolVar(&o.DisableDestructive, "disable-destructive", o.DisableDestructive, "If true, tools annotated with destructiveHint=true are disabled") @@ -237,9 +237,9 @@ func (m *MCPServerOptions) Validate() error { } func (m *MCPServerOptions) Run() error { - profile := mcp.ProfileFromString(m.Profile) - if profile == nil { - return fmt.Errorf("invalid profile name: %s, valid names are: %s", m.Profile, strings.Join(mcp.ProfileNames, ", ")) + toolset := mcp.ToolsetFromString(m.Toolset) + if toolset == nil { + return fmt.Errorf("invalid toolset name: %s, valid names are: %s", m.Toolset, strings.Join(mcp.ToolsetNames, ", ")) } listOutput := output.FromString(m.StaticConfig.ListOutput) if listOutput == nil { @@ -247,7 +247,7 @@ func (m *MCPServerOptions) Run() error { } klog.V(1).Info("Starting kubernetes-mcp-server") klog.V(1).Infof(" - Config: %s", m.ConfigPath) - klog.V(1).Infof(" - Profile: %s", profile.GetName()) + klog.V(1).Infof(" - Toolset: %s", toolset.GetName()) klog.V(1).Infof(" - ListOutput: %s", listOutput.GetName()) klog.V(1).Infof(" - Read-only mode: %t", m.StaticConfig.ReadOnly) klog.V(1).Infof(" - Disable destructive tools: %t", m.StaticConfig.DisableDestructive) @@ -291,7 +291,7 @@ func (m *MCPServerOptions) Run() error { } mcpServer, err := mcp.NewServer(mcp.Configuration{ - Profile: profile, + Toolset: toolset, ListOutput: listOutput, StaticConfig: m.StaticConfig, }) diff --git a/pkg/kubernetes-mcp-server/cmd/root_test.go b/pkg/kubernetes-mcp-server/cmd/root_test.go index 31d79ac5..455e0f29 100644 --- a/pkg/kubernetes-mcp-server/cmd/root_test.go +++ b/pkg/kubernetes-mcp-server/cmd/root_test.go @@ -129,32 +129,32 @@ func TestConfig(t *testing.T) { }) } -func TestProfile(t *testing.T) { +func TestToolset(t *testing.T) { t.Run("available", func(t *testing.T) { ioStreams, _ := testStream() 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) ") { - t.Fatalf("Expected all available profiles, got %s %v", o, err) + if !strings.Contains(o, "MCP toolset to use (one of: full) ") { + t.Fatalf("Expected all available toolsets, got %s %v", o, err) } }) t.Run("default", func(t *testing.T) { ioStreams, out := testStream() rootCmd := NewMCPServer(ioStreams) rootCmd.SetArgs([]string{"--version", "--log-level=1"}) - if err := rootCmd.Execute(); !strings.Contains(out.String(), "- Profile: full") { - t.Fatalf("Expected profile 'full', got %s %v", out, err) + if err := rootCmd.Execute(); !strings.Contains(out.String(), "- Toolset: full") { + t.Fatalf("Expected toolset 'full', got %s %v", out, err) } }) - t.Run("set with --profile", func(t *testing.T) { + t.Run("set with --toolset", func(t *testing.T) { ioStreams, out := testStream() rootCmd := NewMCPServer(ioStreams) - rootCmd.SetArgs([]string{"--version", "--log-level=1", "--profile", "full"}) // TODO: change by some non-default profile + rootCmd.SetArgs([]string{"--version", "--log-level=1", "--toolset", "full"}) // TODO: change by some non-default toolset _ = rootCmd.Execute() - expected := `(?m)\" - Profile\: full\"` + expected := `(?m)\" - Toolset\: full\"` if m, err := regexp.MatchString(expected, out.String()); !m || err != nil { - t.Fatalf("Expected profile to be %s, got %s %v", expected, out.String(), err) + t.Fatalf("Expected toolset to be %s, got %s %v", expected, out.String(), err) } }) } diff --git a/pkg/mcp/common_test.go b/pkg/mcp/common_test.go index c372a140..177fb40f 100644 --- a/pkg/mcp/common_test.go +++ b/pkg/mcp/common_test.go @@ -103,7 +103,7 @@ func TestMain(m *testing.M) { } type mcpContext struct { - profile Profile + toolset Toolset listOutput output.Output logLevel int @@ -126,8 +126,8 @@ func (c *mcpContext) beforeEach(t *testing.T) { c.ctx, c.cancel = context.WithCancel(t.Context()) c.tempDir = t.TempDir() c.withKubeConfig(nil) - if c.profile == nil { - c.profile = &FullProfile{} + if c.toolset == nil { + c.toolset = &Full{} } if c.listOutput == nil { c.listOutput = output.Yaml @@ -149,7 +149,7 @@ func (c *mcpContext) beforeEach(t *testing.T) { klog.SetLogger(textlogger.NewLogger(textlogger.NewConfig(textlogger.Verbosity(c.logLevel), textlogger.Output(&c.logBuffer)))) // MCP Server if c.mcpServer, err = NewServer(Configuration{ - Profile: c.profile, + Toolset: c.toolset, ListOutput: c.listOutput, StaticConfig: c.staticConfig, }); err != nil { @@ -188,7 +188,7 @@ func (c *mcpContext) afterEach() { } func testCase(t *testing.T, test func(c *mcpContext)) { - testCaseWithContext(t, &mcpContext{profile: &FullProfile{}}, test) + testCaseWithContext(t, &mcpContext{toolset: &Full{}}, test) } func testCaseWithContext(t *testing.T, mcpCtx *mcpContext, test func(c *mcpContext)) { diff --git a/pkg/mcp/mcp.go b/pkg/mcp/mcp.go index 54a9da89..0a93b0f5 100644 --- a/pkg/mcp/mcp.go +++ b/pkg/mcp/mcp.go @@ -24,7 +24,7 @@ type ContextKey string const TokenScopesContextKey = ContextKey("TokenScopesContextKey") type Configuration struct { - Profile Profile + Toolset Toolset ListOutput output.Output StaticConfig *config.StaticConfig @@ -89,7 +89,7 @@ func (s *Server) reloadKubernetesClient() error { } s.k = k applicableTools := make([]server.ServerTool, 0) - for _, tool := range s.configuration.Profile.GetTools(s) { + for _, tool := range s.configuration.Toolset.GetTools(s) { if !s.configuration.isToolApplicable(tool) { continue } diff --git a/pkg/mcp/profiles.go b/pkg/mcp/profiles.go deleted file mode 100644 index 6c0d9741..00000000 --- a/pkg/mcp/profiles.go +++ /dev/null @@ -1,54 +0,0 @@ -package mcp - -import ( - "slices" - - "github.com/mark3labs/mcp-go/server" -) - -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 { - 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()) - } -} diff --git a/pkg/mcp/toolsets.go b/pkg/mcp/toolsets.go new file mode 100644 index 00000000..96010804 --- /dev/null +++ b/pkg/mcp/toolsets.go @@ -0,0 +1,54 @@ +package mcp + +import ( + "slices" + + "github.com/mark3labs/mcp-go/server" +) + +type Toolset interface { + GetName() string + GetDescription() string + GetTools(s *Server) []server.ServerTool +} + +var Toolsets = []Toolset{ + &Full{}, +} + +var ToolsetNames []string + +func ToolsetFromString(name string) Toolset { + for _, toolset := range Toolsets { + if toolset.GetName() == name { + return toolset + } + } + return nil +} + +type Full struct{} + +func (p *Full) GetName() string { + return "full" +} +func (p *Full) GetDescription() string { + return "Complete toolset with all tools and extended outputs" +} +func (p *Full) GetTools(s *Server) []server.ServerTool { + return slices.Concat( + s.initConfiguration(), + s.initEvents(), + s.initNamespaces(), + s.initPods(), + s.initResources(), + s.initHelm(), + ) +} + +func init() { + ToolsetNames = make([]string, 0) + for _, toolset := range Toolsets { + ToolsetNames = append(ToolsetNames, toolset.GetName()) + } +} diff --git a/pkg/mcp/profiles_test.go b/pkg/mcp/toolsets_test.go similarity index 92% rename from pkg/mcp/profiles_test.go rename to pkg/mcp/toolsets_test.go index e0bc1b42..2927cd04 100644 --- a/pkg/mcp/profiles_test.go +++ b/pkg/mcp/toolsets_test.go @@ -8,7 +8,7 @@ import ( "github.com/mark3labs/mcp-go/mcp" ) -func TestFullProfileTools(t *testing.T) { +func TestFullToolsetTools(t *testing.T) { expectedNames := []string{ "configuration_view", "events_list", @@ -29,7 +29,7 @@ func TestFullProfileTools(t *testing.T) { "resources_create_or_update", "resources_delete", } - mcpCtx := &mcpContext{profile: &FullProfile{}} + mcpCtx := &mcpContext{toolset: &Full{}} testCaseWithContext(t, mcpCtx, func(c *mcpContext) { tools, err := c.mcpClient.ListTools(c.ctx, mcp.ListToolsRequest{}) t.Run("ListTools returns tools", func(t *testing.T) { @@ -53,9 +53,9 @@ func TestFullProfileTools(t *testing.T) { }) } -func TestFullProfileToolsInOpenShift(t *testing.T) { +func TestFullToolsetToolsInOpenShift(t *testing.T) { mcpCtx := &mcpContext{ - profile: &FullProfile{}, + toolset: &Full{}, before: inOpenShift, after: inOpenShiftClear, }