Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions cmd/docker-mcp/commands/workingset.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func workingSetCommand() *cobra.Command {
cmd.AddCommand(removeWorkingSetCommand())
cmd.AddCommand(workingsetServerCommand())
cmd.AddCommand(configWorkingSetCommand())
cmd.AddCommand(toolsWorkingSetCommand())
return cmd
}

Expand Down Expand Up @@ -68,6 +69,60 @@ func configWorkingSetCommand() *cobra.Command {
return cmd
}

func toolsWorkingSetCommand() *cobra.Command {
var enable []string
var disable []string
var enableAll []string
var disableAll []string

cmd := &cobra.Command{
Use: "tools <working-set-id> [--enable <tool> ...] [--disable <tool> ...] [--enable-all <server> ...] [--disable-all <server> ...]",
Short: "Manage tool allowlist for servers in a working set",
Long: `Manage the tool allowlist for servers in a working set.
Tools are specified using dot notation: <serverName>.<toolName>

Use --enable to enable specific tools for a server (can be specified multiple times).
Use --disable to disable specific tools for a server (can be specified multiple times).
Use --enable-all to enable all tools for a server (can be specified multiple times).
Use --disable-all to disable all tools for a server (can be specified multiple times).

To view enabled tools, use: docker mcp workingset show <working-set-id>`,
Example: ` # Enable specific tools for a server
docker mcp workingset tools my-set --enable github.create_issue --enable github.list_repos

# Disable specific tools for a server
docker mcp workingset tools my-set --disable github.create_issue --disable github.search_code

# Enable and disable in one command
docker mcp workingset tools my-set --enable github.create_issue --disable github.search_code

# Enable all tools for a server
docker mcp workingset tools my-set --enable-all github

# Disable all tools for a server
docker mcp workingset tools my-set --disable-all github

# View all enabled tools in the working set
docker mcp workingset show my-set`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
dao, err := db.New()
if err != nil {
return err
}
return workingset.UpdateTools(cmd.Context(), dao, args[0], enable, disable, enableAll, disableAll)
},
}

flags := cmd.Flags()
flags.StringArrayVar(&enable, "enable", []string{}, "Enable specific tools: <serverName>.<toolName> (repeatable)")
flags.StringArrayVar(&disable, "disable", []string{}, "Disable specific tools: <serverName>.<toolName> (repeatable)")
flags.StringArrayVar(&enableAll, "enable-all", []string{}, "Enable all tools for a server: <serverName> (repeatable)")
flags.StringArrayVar(&disableAll, "disable-all", []string{}, "Disable all tools for a server: <serverName> (repeatable)")

return cmd
}

func createWorkingSetCommand() *cobra.Command {
var opts struct {
ID string
Expand Down
2 changes: 1 addition & 1 deletion pkg/db/workingset.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ type Server struct {
Type string `json:"type"`
Config map[string]any `json:"config,omitempty"`
Secrets string `json:"secrets,omitempty"`
Tools []string `json:"tools,omitempty"`
Tools []string `json:"tools"`
Source string `json:"source,omitempty"`
Image string `json:"image,omitempty"`

Expand Down
127 changes: 127 additions & 0 deletions pkg/workingset/tools.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package workingset

import (
"context"
"database/sql"
"errors"
"fmt"
"slices"
"strings"

"github.com/docker/mcp-gateway/pkg/db"
)

func UpdateTools(ctx context.Context, dao db.DAO, id string, enable, disable, enableAll, disableAll []string) error {
if len(enable) == 0 && len(disable) == 0 && len(enableAll) == 0 && len(disableAll) == 0 {
return fmt.Errorf("must provide at least one flag: --enable, --disable, --enable-all, or --disable-all")
}
dbWorkingSet, err := dao.GetWorkingSet(ctx, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fmt.Errorf("working set %s not found", id)
}
return fmt.Errorf("failed to get working set: %w", err)
}
workingSet := NewFromDb(dbWorkingSet)

// Handle enable-all for specified servers
enableAllCount := 0
for _, serverName := range enableAll {
server := workingSet.FindServer(serverName)
if server == nil {
return fmt.Errorf("server %s not found in working set", serverName)
}
if server.Tools != nil {
server.Tools = nil
enableAllCount++
}
}

// Handle disable-all for specified servers
disableAllCount := 0
for _, serverName := range disableAll {
server := workingSet.FindServer(serverName)
if server == nil {
return fmt.Errorf("server %s not found in working set", serverName)
}
if server.Tools == nil || len(server.Tools) > 0 {
server.Tools = []string{}
disableAllCount++
}
}

// Check for overlap between enable and disable sets
enableSet := make(map[string]bool)
for _, toolArg := range enable {
enableSet[toolArg] = true
}
disableSet := make(map[string]bool)
for _, toolArg := range disable {
disableSet[toolArg] = true
}

var overlapping []string
for tool := range enableSet {
if disableSet[tool] {
overlapping = append(overlapping, tool)
}
}

enabledCount := 0
for _, toolArg := range enable {
serverName, toolName, found := strings.Cut(toolArg, ".")
if !found {
return fmt.Errorf("invalid tool argument: %s, expected <serverName>.<toolName>", toolArg)
}
server := workingSet.FindServer(serverName)
if server == nil {
return fmt.Errorf("server %s not found in working set for argument %s", serverName, toolArg)
}
if !slices.Contains(server.Tools, toolName) {
server.Tools = append(server.Tools, toolName)
enabledCount++
}
}

disabledCount := 0
for _, toolArg := range disable {
serverName, toolName, found := strings.Cut(toolArg, ".")
if !found {
return fmt.Errorf("invalid tool argument: %s, expected <serverName>.<toolName>", toolArg)
}
server := workingSet.FindServer(serverName)
if server == nil {
return fmt.Errorf("server %s not found in working set for argument %s", serverName, toolArg)
}
if idx := slices.Index(server.Tools, toolName); idx != -1 {
server.Tools = slices.Delete(server.Tools, idx, idx+1)
disabledCount++
}
}

err = dao.UpdateWorkingSet(ctx, workingSet.ToDb())
if err != nil {
return fmt.Errorf("failed to update working set: %w", err)
}

if enabledCount == 0 && disabledCount == 0 && enableAllCount == 0 && disableAllCount == 0 {
fmt.Printf("No changes made to working set %s\n", id)
} else {
if enableAllCount > 0 {
fmt.Printf("Enabled all tools for %d server(s) in working set %s\n", enableAllCount, id)
}
if disableAllCount > 0 {
fmt.Printf("Disabled all tools for %d server(s) in working set %s\n", disableAllCount, id)
}
if enabledCount > 0 || disabledCount > 0 {
fmt.Printf("Updated working set %s: %d tool(s) enabled, %d tool(s) disabled\n", id, enabledCount, disabledCount)
}
}

if len(overlapping) > 0 {
slices.Sort(overlapping)
fmt.Printf("Warning: The following tool(s) were both enabled and disabled in the same operation: %s\n", strings.Join(overlapping, ", "))
}

return nil
}
Loading
Loading