refactor(toolsets): renamed Profile to Toolset (#309)

As a prior step to providing support for toolsets
this change repurposes the current work in profiles
which partially aligns with the toolsets expected features

Signed-off-by: Marc Nuri <marc@marcnuri.com>
This commit is contained in:
Marc Nuri
2025-09-11 09:25:09 +02:00
committed by GitHub
parent 467e7e6757
commit 10c82f7bff
8 changed files with 84 additions and 84 deletions

View File

@@ -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 {

View File

@@ -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,
})

View File

@@ -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)
}
})
}

View File

@@ -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)) {

View File

@@ -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
}

View File

@@ -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())
}
}

54
pkg/mcp/toolsets.go Normal file
View File

@@ -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())
}
}

View File

@@ -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,
}