Skip to content

Commit 11775d8

Browse files
Change dynamic-tools feature to opt-in and streamline tools
The dynamic-tools feature now defaults to disabled instead of enabled, making it an opt-in capability. This provides better control over when automatic server management tools are available. Key changes: - Default dynamic-tools to disabled in feature flags and gateway logic - Auto-disable dynamic-tools when --servers flag is used (explicit mode) - Streamline available dynamic tools (remove mcp-registry-import, mcp-session-name, mcp-discover prompt) - Adjust mcp-add to only send tool JSON for Claude clients - Add cagent example configuration - Update documentation to reflect new behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent aa873b5 commit 11775d8

File tree

8 files changed

+100
-34
lines changed

8 files changed

+100
-34
lines changed

cmd/docker-mcp/commands/feature.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ func isFeatureEnabledFromConfig(configFile *configfile.ConfigFile, feature strin
210210
// Features that are enabled by default
211211
defaultEnabledFeatures := map[string]bool{
212212
"mcp-oauth-dcr": true,
213-
"dynamic-tools": true,
214213
}
215214

216215
if configFile.Features == nil {

cmd/docker-mcp/commands/feature_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,15 +63,15 @@ func TestIsFeatureEnabledDynamicTools(t *testing.T) {
6363
Features: make(map[string]string),
6464
}
6565
enabled := isFeatureEnabledFromConfig(configFile, "dynamic-tools")
66-
assert.True(t, enabled, "dynamic-tools should default to enabled when missing")
66+
assert.False(t, enabled, "dynamic-tools should default to disabled when missing")
6767
})
6868

6969
t.Run("nil features map", func(t *testing.T) {
7070
configFile := &configfile.ConfigFile{
7171
Features: nil,
7272
}
7373
enabled := isFeatureEnabledFromConfig(configFile, "dynamic-tools")
74-
assert.True(t, enabled, "dynamic-tools should default to enabled when Features is nil")
74+
assert.False(t, enabled, "dynamic-tools should default to disabled when Features is nil")
7575
})
7676
}
7777

cmd/docker-mcp/commands/gateway.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,18 @@ func gatewayCommand(docker docker.Client, dockerCli command.Cli) *cobra.Command
152152
options.ServerNames = allServerNames
153153
}
154154

155+
// Disable dynamic-tools if the user explicitly configured a set of servers via the --servers flag.
156+
// When users specify servers explicitly, they're operating in a more manual mode
157+
// and may not want the automatic server management tools (mcp-find, mcp-add, mcp-remove).
158+
if len(options.ServerNames) > 0 && !enableAllServers {
159+
if options.DynamicTools {
160+
options.DynamicTools = false
161+
if options.Verbose {
162+
fmt.Fprintln(dockerCli.Err(), "Note: dynamic-tools disabled when using --servers flag")
163+
}
164+
}
165+
}
166+
155167
return gateway.NewGateway(options, docker).Run(cmd.Context())
156168
},
157169
}
@@ -308,12 +320,12 @@ func isMcpOAuthDcrFeatureEnabled(dockerCli command.Cli) bool {
308320
func isDynamicToolsFeatureEnabled(dockerCli command.Cli) bool {
309321
configFile := dockerCli.ConfigFile()
310322
if configFile == nil || configFile.Features == nil {
311-
return true // Default enabled when no config exists
323+
return false // Default disabled when no config exists
312324
}
313325

314326
value, exists := configFile.Features["dynamic-tools"]
315327
if !exists {
316-
return true // Default enabled when not in config
328+
return false // Default disabled when not in config
317329
}
318330

319331
return value == "enabled"

cmd/docker-mcp/commands/gateway_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -88,31 +88,31 @@ func TestIsDynamicToolsFeatureEnabled(t *testing.T) {
8888
Features: map[string]string{},
8989
}
9090
enabled := isDynamicToolsFeatureEnabledFromConfig(configFile)
91-
assert.True(t, enabled, "should return true when dynamic-tools is not set")
91+
assert.False(t, enabled, "should return false when dynamic-tools is not set")
9292
})
9393

9494
t.Run("nil config", func(t *testing.T) {
9595
enabled := isDynamicToolsFeatureEnabledFromConfig(nil)
96-
assert.True(t, enabled, "should return true when config is nil")
96+
assert.False(t, enabled, "should return false when config is nil")
9797
})
9898

9999
t.Run("nil features", func(t *testing.T) {
100100
configFile := &configfile.ConfigFile{
101101
Features: nil,
102102
}
103103
enabled := isDynamicToolsFeatureEnabledFromConfig(configFile)
104-
assert.True(t, enabled, "should return true when features is nil")
104+
assert.False(t, enabled, "should return false when features is nil")
105105
})
106106
}
107107

108108
// Helper function for testing (extract logic from isDynamicToolsFeatureEnabled)
109109
func isDynamicToolsFeatureEnabledFromConfig(configFile *configfile.ConfigFile) bool {
110110
if configFile == nil || configFile.Features == nil {
111-
return true // Default enabled when no config exists
111+
return false // Default disabled when no config exists
112112
}
113113
value, exists := configFile.Features["dynamic-tools"]
114114
if !exists {
115-
return true // Default enabled when not in config
115+
return false // Default disabled when not in config
116116
}
117117
return value == "enabled"
118118
}

docs/feature-specs/dynamic_servers.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ docker config view | grep dynamic-tools
1818

1919
When enabled, the gateway adds five internal management tools to the available tool set.
2020

21+
**Note**: Dynamic tools are automatically disabled when using the `--servers` flag to explicitly specify which servers to run. This is because explicit server configuration indicates a manual mode where automatic server management tools are not needed.
22+
2123
## Available Tools
2224

2325
### 1. mcp-find

examples/cagent/agent.yaml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
version: v2
2+
3+
agents:
4+
root:
5+
model: claude
6+
description: Root orchestrator agent that coordinates tasks and delegates to specialized sub-agents
7+
instruction: |
8+
You are a specialized agent with access to MCP (Model Context Protocol) server tools
9+
running through the Docker MCP Gateway.
10+
11+
Your capabilities include:
12+
- Searching for MCP servers that might help with my task
13+
- Managing and discovering available MCP tools
14+
- Providing detailed results from tool executions
15+
16+
When executing MCP tools:
17+
1. List available tools if unsure what's available
18+
2. Use the appropriate tool with correct parameters
19+
3. Return clear, structured results to the parent agent
20+
4. Handle errors gracefully and suggest alternatives
21+
toolsets:
22+
- type: filesystem # Grants the agent filesystem access
23+
- type: todo # Enable the todo list tool
24+
- type: mcp
25+
command: docker
26+
args:
27+
- mcp
28+
- gateway
29+
- run
30+
- --log=/Users/slim/gateway.log
31+
32+
models:
33+
claude:
34+
provider: anthropic
35+
model: claude-sonnet-4-0
36+
max_tokens: 64000
37+
thinking_budget: medium

pkg/gateway/dynamic_mcps.go

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -688,9 +688,17 @@ func (g *Gateway) createMcpAddTool(clientConfig *clientConfig) *ToolRegistration
688688
// Build the response text
689689
responseText := fmt.Sprintf("Successfully added %d tools in server '%s'. Assume that it is fully configured and ready to use.", len(addedTools), serverName)
690690

691-
// Include the JSON representation of the newly added tools
692-
// This is useful when the client session does not support tool change notifications
693-
if len(addedTools) > 0 {
691+
// Get client name
692+
clientName := ""
693+
if req.Session.InitializeParams().ClientInfo != nil {
694+
clientName = req.Session.InitializeParams().ClientInfo.Name
695+
}
696+
697+
// Include the JSON representation of the newly added tools if client name contains "cagent" or "claude"
698+
clientNameLower := strings.ToLower(clientName)
699+
shouldSendTools := len(addedTools) > 0 && strings.Contains(clientNameLower, "claude")
700+
701+
if shouldSendTools {
694702
// Create a tools list response matching the format from tools/list
695703
toolsList := make([]map[string]any, 0, len(addedTools))
696704
for _, tool := range addedTools {
@@ -808,6 +816,7 @@ func (g *Gateway) createMcpRemoveTool() *ToolRegistration {
808816
}
809817
}
810818

819+
//nolint:unused
811820
func (g *Gateway) createMcpRegistryImportTool(configuration Configuration, _ *clientConfig) *ToolRegistration {
812821
tool := &mcp.Tool{
813822
Name: "mcp-registry-import",
@@ -943,6 +952,8 @@ func (g *Gateway) createMcpRegistryImportTool(configuration Configuration, _ *cl
943952
}
944953

945954
// readServersFromURL fetches and parses server definitions from a URL
955+
//
956+
//nolint:unused
946957
func (g *Gateway) readServersFromURL(ctx context.Context, url string) (map[string]catalog.Server, error) {
947958
servers := make(map[string]catalog.Server)
948959

@@ -1098,6 +1109,8 @@ func (g *Gateway) createMcpConfigSetTool(clientConfig *clientConfig) *ToolRegist
10981109
}
10991110

11001111
// createMcpSessionNameTool implements a tool for setting the session name
1112+
//
1113+
//nolint:unused
11011114
func (g *Gateway) createMcpSessionNameTool() *ToolRegistration {
11021115
tool := &mcp.Tool{
11031116
Name: "mcp-session-name",
@@ -1170,6 +1183,8 @@ func (g *Gateway) createMcpSessionNameTool() *ToolRegistration {
11701183
}
11711184

11721185
// isValidSessionName checks if a session name contains only alphanumeric characters and hyphens
1186+
//
1187+
//nolint:unused
11731188
func isValidSessionName(name string) bool {
11741189
for _, ch := range name {
11751190
if (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z') && (ch < '0' || ch > '9') && ch != '-' && ch != '_' {

pkg/gateway/reload.go

Lines changed: 22 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99
"github.com/modelcontextprotocol/go-sdk/mcp"
1010

1111
"github.com/docker/mcp-gateway/pkg/log"
12-
"github.com/docker/mcp-gateway/pkg/prompts"
12+
// "github.com/docker/mcp-gateway/pkg/prompts"
1313
)
1414

1515
func (g *Gateway) reloadConfiguration(ctx context.Context, configuration Configuration, serverNames []string, clientConfig *clientConfig) error {
@@ -95,42 +95,43 @@ func (g *Gateway) reloadConfiguration(ctx context.Context, configuration Configu
9595
g.mcpServer.AddTool(mcpRemoveTool.Tool, mcpRemoveTool.Handler)
9696
g.toolRegistrations[mcpRemoveTool.Tool.Name] = *mcpRemoveTool
9797

98-
// Add mcp-registry-import tool
99-
mcpRegistryImportTool := g.createMcpRegistryImportTool(configuration, clientConfig)
100-
g.mcpServer.AddTool(mcpRegistryImportTool.Tool, mcpRegistryImportTool.Handler)
101-
g.toolRegistrations[mcpRegistryImportTool.Tool.Name] = *mcpRegistryImportTool
102-
103-
// Add mcp-config-set tool
104-
mcpConfigSetTool := g.createMcpConfigSetTool(clientConfig)
105-
g.mcpServer.AddTool(mcpConfigSetTool.Tool, mcpConfigSetTool.Handler)
106-
g.toolRegistrations[mcpConfigSetTool.Tool.Name] = *mcpConfigSetTool
107-
108-
// Add mcp-session-name tool
109-
mcpSessionNameTool := g.createMcpSessionNameTool()
110-
g.mcpServer.AddTool(mcpSessionNameTool.Tool, mcpSessionNameTool.Handler)
111-
g.toolRegistrations[mcpSessionNameTool.Tool.Name] = *mcpSessionNameTool
112-
11398
// Add codemode
11499
codeModeTool := g.createCodeModeTool(clientConfig)
115100
g.mcpServer.AddTool(codeModeTool.Tool, codeModeTool.Handler)
116101
g.toolRegistrations[codeModeTool.Tool.Name] = *codeModeTool
117102

118-
// Add mcp-exec tool
103+
// Add mcp-exec tool only if client name contains "claude"
119104
mcpExecTool := g.createMcpExecTool()
120105
g.mcpServer.AddTool(mcpExecTool.Tool, mcpExecTool.Handler)
121106
g.toolRegistrations[mcpExecTool.Tool.Name] = *mcpExecTool
122107

123-
prompts.AddDiscoverPrompt(g.mcpServer)
108+
// Add mcp-config-set tool
109+
mcpConfigSetTool := g.createMcpConfigSetTool(clientConfig)
110+
g.mcpServer.AddTool(mcpConfigSetTool.Tool, mcpConfigSetTool.Handler)
111+
g.toolRegistrations[mcpConfigSetTool.Tool.Name] = *mcpConfigSetTool
124112

125-
log.Log(" > mcp-discover: prompt for learning about dynamic server management")
126113
log.Log(" > mcp-find: tool for finding MCP servers in the catalog")
127114
log.Log(" > mcp-add: tool for adding MCP servers to the registry")
128115
log.Log(" > mcp-remove: tool for removing MCP servers from the registry")
129-
log.Log(" > mcp-registry-import: tool for importing servers from MCP registry URLs")
130116
log.Log(" > mcp-config-set: tool for setting configuration values for MCP servers")
131-
log.Log(" > mcp-session-name: tool for setting session name to persist configuration")
132117
log.Log(" > code-mode: write code that calls other MCPs directly")
133118
log.Log(" > mcp-exec: execute tools that exist in the current session")
119+
120+
// Add mcp-registry-import tool
121+
// mcpRegistryImportTool := g.createMcpRegistryImportTool(configuration, clientConfig)
122+
// g.mcpServer.AddTool(mcpRegistryImportTool.Tool, mcpRegistryImportTool.Handler)
123+
// g.toolRegistrations[mcpRegistryImportTool.Tool.Name] = *mcpRegistryImportTool
124+
125+
// Add mcp-session-name tool
126+
// mcpSessionNameTool := g.createMcpSessionNameTool()
127+
// g.mcpServer.AddTool(mcpSessionNameTool.Tool, mcpSessionNameTool.Handler)
128+
// g.toolRegistrations[mcpSessionNameTool.Tool.Name] = *mcpSessionNameTool
129+
// log.Log(" > mcp-registry-import: tool for importing servers from MCP registry URLs")
130+
// log.Log(" > mcp-session-name: tool for setting session name to persist configuration")
131+
132+
// Add prompt
133+
// prompts.AddDiscoverPrompt(g.mcpServer)
134+
// log.Log(" > mcp-discover: prompt for learning about dynamic server management")
134135
}
135136

136137
for _, prompt := range capabilities.Prompts {

0 commit comments

Comments
 (0)