pkg/toolsets/toolsets.go (144 lines of code) (raw):
package toolsets
import (
"fmt"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
func NewServerTool(tool mcp.Tool, handler server.ToolHandlerFunc) server.ServerTool {
return server.ServerTool{Tool: tool, Handler: handler}
}
type Toolset struct {
Name string
Description string
Enabled bool
readOnly bool
writeTools []server.ServerTool
readTools []server.ServerTool
}
func (t *Toolset) GetActiveTools() []server.ServerTool {
if t.Enabled {
if t.readOnly {
return t.readTools
}
return append(t.readTools, t.writeTools...)
}
return nil
}
func (t *Toolset) GetAvailableTools() []server.ServerTool {
if t.readOnly {
return t.readTools
}
return append(t.readTools, t.writeTools...)
}
func (t *Toolset) RegisterTools(s *server.MCPServer) {
if !t.Enabled {
return
}
for _, tool := range t.readTools {
s.AddTool(tool.Tool, tool.Handler)
}
if !t.readOnly {
for _, tool := range t.writeTools {
s.AddTool(tool.Tool, tool.Handler)
}
}
}
func (t *Toolset) SetReadOnly() {
// Set the toolset to read-only
t.readOnly = true
}
func (t *Toolset) AddWriteTools(tools ...server.ServerTool) *Toolset {
// Silently ignore if the toolset is read-only to avoid any breach of that contract
for _, tool := range tools {
if tool.Tool.Annotations.ReadOnlyHint {
panic(fmt.Sprintf("tool (%s) is incorrectly annotated as read-only", tool.Tool.Name))
}
}
if !t.readOnly {
t.writeTools = append(t.writeTools, tools...)
}
return t
}
func (t *Toolset) AddReadTools(tools ...server.ServerTool) *Toolset {
for _, tool := range tools {
if !tool.Tool.Annotations.ReadOnlyHint {
panic(fmt.Sprintf("tool (%s) must be annotated as read-only", tool.Tool.Name))
}
tool.Tool.Annotations = mcp.ToolAnnotation{
ReadOnlyHint: true,
Title: tool.Tool.Annotations.Title,
}
}
t.readTools = append(t.readTools, tools...)
return t
}
type ToolsetGroup struct {
Toolsets map[string]*Toolset
everythingOn bool
readOnly bool
}
func NewToolsetGroup(readOnly bool) *ToolsetGroup {
return &ToolsetGroup{
Toolsets: make(map[string]*Toolset),
everythingOn: false,
readOnly: readOnly,
}
}
func (tg *ToolsetGroup) AddToolset(ts *Toolset) {
if tg.readOnly {
ts.SetReadOnly()
}
tg.Toolsets[ts.Name] = ts
}
func NewToolset(name string, description string) *Toolset {
return &Toolset{
Name: name,
Description: description,
Enabled: false,
readOnly: false,
}
}
func (tg *ToolsetGroup) IsEnabled(name string) bool {
// If everythingOn is true, all features are enabled
if tg.everythingOn {
return true
}
feature, exists := tg.Toolsets[name]
if !exists {
return false
}
return feature.Enabled
}
func (tg *ToolsetGroup) EnableToolsets(names []string) error {
// Special case for "all"
for _, name := range names {
if name == "all" {
tg.everythingOn = true
break
}
err := tg.EnableToolset(name)
if err != nil {
return err
}
}
// Do this after to ensure all toolsets are enabled if "all" is present anywhere in list
if tg.everythingOn {
for name := range tg.Toolsets {
err := tg.EnableToolset(name)
if err != nil {
return err
}
}
return nil
}
return nil
}
func (tg *ToolsetGroup) EnableToolset(name string) error {
toolset, exists := tg.Toolsets[name]
if !exists {
return fmt.Errorf("toolset %s does not exist", name)
}
toolset.Enabled = true
tg.Toolsets[name] = toolset
return nil
}
func (tg *ToolsetGroup) RegisterTools(s *server.MCPServer) {
for _, toolset := range tg.Toolsets {
toolset.RegisterTools(s)
}
}