mirror of
https://github.com/containers/kubernetes-mcp-server.git
synced 2025-10-23 01:22:57 +03:00
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:
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
54
pkg/mcp/toolsets.go
Normal 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())
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
Reference in New Issue
Block a user