Skip to content

Commit 89acd0b

Browse files
--user-configured-catalogs is now the default
1 parent 754c023 commit 89acd0b

File tree

4 files changed

+74
-388
lines changed

4 files changed

+74
-388
lines changed

cmd/docker-mcp/commands/feature.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ func featureEnableCommand(dockerCli command.Cli) *cobra.Command {
3737
Long: `Enable an experimental feature.
3838
3939
Available features:
40-
configured-catalogs Allow gateway to use user-managed catalogs alongside Docker catalog
4140
oauth-interceptor Enable GitHub OAuth flow interception for automatic authentication
4241
dynamic-tools Enable internal MCP management tools (mcp-find, mcp-add, mcp-remove)`,
4342
Args: cobra.ExactArgs(1),
@@ -46,7 +45,7 @@ Available features:
4645

4746
// Validate feature name
4847
if !isKnownFeature(featureName) {
49-
return fmt.Errorf("unknown feature: %s\n\nAvailable features:\n configured-catalogs Allow gateway to use user-managed catalogs\n oauth-interceptor Enable GitHub OAuth flow interception\n dynamic-tools Enable internal MCP management tools", featureName)
48+
return fmt.Errorf("unknown feature: %s\n\nAvailable features:\n oauth-interceptor Enable GitHub OAuth flow interception\n dynamic-tools Enable internal MCP management tools", featureName)
5049
}
5150

5251
// Enable the feature
@@ -65,12 +64,6 @@ Available features:
6564

6665
// Provide usage hints for features
6766
switch featureName {
68-
case "configured-catalogs":
69-
fmt.Println("\nTo use configured catalogs with the gateway, run:")
70-
fmt.Println(" docker mcp gateway run --use-configured-catalogs")
71-
fmt.Println("\nTo create and manage catalogs, use:")
72-
fmt.Println(" docker mcp catalog create <name>")
73-
fmt.Println(" docker mcp catalog add <catalog> <server-name> <server-file>")
7467
case "oauth-interceptor":
7568
fmt.Println("\nThis feature enables automatic GitHub OAuth interception when 401 errors occur.")
7669
fmt.Println("When enabled, the gateway will automatically provide OAuth URLs for authentication.")
@@ -135,7 +128,7 @@ func featureListCommand(dockerCli command.Cli) *cobra.Command {
135128
fmt.Println()
136129

137130
// Show all known features
138-
knownFeatures := []string{"configured-catalogs", "oauth-interceptor", "dynamic-tools"}
131+
knownFeatures := []string{"oauth-interceptor", "dynamic-tools"}
139132
for _, feature := range knownFeatures {
140133
status := "disabled"
141134
if isFeatureEnabledFromCli(dockerCli, feature) {
@@ -146,8 +139,6 @@ func featureListCommand(dockerCli command.Cli) *cobra.Command {
146139

147140
// Add description for each feature
148141
switch feature {
149-
case "configured-catalogs":
150-
fmt.Printf(" %-20s %s\n", "", "Allow gateway to use user-managed catalogs alongside Docker catalog")
151142
case "oauth-interceptor":
152143
fmt.Printf(" %-20s %s\n", "", "Enable GitHub OAuth flow interception for automatic authentication")
153144
case "dynamic-tools":
@@ -212,7 +203,6 @@ func isFeatureEnabledFromConfig(configFile *configfile.ConfigFile, feature strin
212203
// isKnownFeature checks if the feature name is valid
213204
func isKnownFeature(feature string) bool {
214205
knownFeatures := []string{
215-
"configured-catalogs",
216206
"oauth-interceptor",
217207
"dynamic-tools",
218208
}
Lines changed: 64 additions & 211 deletions
Original file line numberDiff line numberDiff line change
@@ -1,226 +1,79 @@
11
package commands
22

33
import (
4-
"encoding/json"
5-
"os"
6-
"path/filepath"
7-
"strconv"
84
"testing"
95

106
"github.com/docker/cli/cli/config/configfile"
117
"github.com/stretchr/testify/assert"
12-
"github.com/stretchr/testify/require"
138
)
149

15-
func TestIsFeatureEnabledTrue(t *testing.T) {
16-
// Create temporary config directory
17-
tempDir := t.TempDir()
18-
configFile := filepath.Join(tempDir, "config.json")
19-
20-
// Create config with enabled feature
21-
config := map[string]any{
22-
"features": map[string]string{
23-
"configured-catalogs": "enabled",
24-
},
25-
}
26-
configData, err := json.Marshal(config)
27-
require.NoError(t, err)
28-
err = os.WriteFile(configFile, configData, 0o644)
29-
require.NoError(t, err)
30-
31-
// Load config file
32-
configFile2 := &configfile.ConfigFile{
33-
Filename: configFile,
34-
}
35-
_ = configFile2.LoadFromReader(os.Stdin) // This will load from the filename
36-
37-
// Test directly with Features map
38-
configFile2.Features = map[string]string{
39-
"configured-catalogs": "enabled",
40-
}
41-
42-
enabled := isFeatureEnabled(configFile2, "configured-catalogs")
43-
assert.True(t, enabled)
44-
}
45-
46-
func TestIsFeatureEnabledFalse(t *testing.T) {
47-
configFile := &configfile.ConfigFile{
48-
Features: map[string]string{
49-
"configured-catalogs": "disabled",
50-
},
51-
}
52-
53-
enabled := isFeatureEnabled(configFile, "configured-catalogs")
54-
assert.False(t, enabled)
55-
}
56-
57-
func TestIsFeatureEnabledMissing(t *testing.T) {
58-
configFile := &configfile.ConfigFile{
59-
Features: make(map[string]string),
60-
}
61-
62-
enabled := isFeatureEnabled(configFile, "configured-catalogs")
63-
assert.False(t, enabled, "missing features should default to disabled")
10+
func TestIsFeatureEnabledOAuthInterceptor(t *testing.T) {
11+
t.Run("enabled", func(t *testing.T) {
12+
configFile := &configfile.ConfigFile{
13+
Features: map[string]string{
14+
"oauth-interceptor": "enabled",
15+
},
16+
}
17+
enabled := isFeatureEnabledFromConfig(configFile, "oauth-interceptor")
18+
assert.True(t, enabled)
19+
})
20+
21+
t.Run("disabled", func(t *testing.T) {
22+
configFile := &configfile.ConfigFile{
23+
Features: map[string]string{
24+
"oauth-interceptor": "disabled",
25+
},
26+
}
27+
enabled := isFeatureEnabledFromConfig(configFile, "oauth-interceptor")
28+
assert.False(t, enabled)
29+
})
30+
31+
t.Run("missing", func(t *testing.T) {
32+
configFile := &configfile.ConfigFile{
33+
Features: make(map[string]string),
34+
}
35+
enabled := isFeatureEnabledFromConfig(configFile, "oauth-interceptor")
36+
assert.False(t, enabled, "missing features should default to disabled")
37+
})
6438
}
6539

66-
func TestIsFeatureEnabledCorrupt(t *testing.T) {
67-
configFile := &configfile.ConfigFile{
68-
Features: map[string]string{
69-
"configured-catalogs": "invalid-boolean",
70-
},
71-
}
72-
73-
enabled := isFeatureEnabled(configFile, "configured-catalogs")
74-
assert.False(t, enabled, "corrupted feature values should default to disabled")
40+
func TestIsFeatureEnabledDynamicTools(t *testing.T) {
41+
t.Run("enabled", func(t *testing.T) {
42+
configFile := &configfile.ConfigFile{
43+
Features: map[string]string{
44+
"dynamic-tools": "enabled",
45+
},
46+
}
47+
enabled := isFeatureEnabledFromConfig(configFile, "dynamic-tools")
48+
assert.True(t, enabled)
49+
})
50+
51+
t.Run("disabled", func(t *testing.T) {
52+
configFile := &configfile.ConfigFile{
53+
Features: map[string]string{
54+
"dynamic-tools": "disabled",
55+
},
56+
}
57+
enabled := isFeatureEnabledFromConfig(configFile, "dynamic-tools")
58+
assert.False(t, enabled)
59+
})
60+
61+
t.Run("missing", func(t *testing.T) {
62+
configFile := &configfile.ConfigFile{
63+
Features: make(map[string]string),
64+
}
65+
enabled := isFeatureEnabledFromConfig(configFile, "dynamic-tools")
66+
assert.False(t, enabled, "missing features should default to disabled")
67+
})
7568
}
7669

77-
func TestEnableFeature(t *testing.T) {
78-
// Create temporary config directory
79-
tempDir := t.TempDir()
80-
configPath := filepath.Join(tempDir, "config.json")
81-
82-
// Create initial config
83-
configFile := &configfile.ConfigFile{
84-
Filename: configPath,
85-
Features: make(map[string]string),
86-
}
87-
88-
// Test enabling configured-catalogs feature
89-
err := enableFeature(configFile, "configured-catalogs")
90-
require.NoError(t, err)
91-
92-
// Verify feature was enabled
93-
enabled := isFeatureEnabled(configFile, "configured-catalogs")
94-
assert.True(t, enabled, "configured-catalogs feature should be enabled")
95-
}
96-
97-
func TestDisableFeature(t *testing.T) {
98-
// Create temporary config directory
99-
tempDir := t.TempDir()
100-
configPath := filepath.Join(tempDir, "config.json")
101-
102-
// Create config with feature already enabled
103-
configFile := &configfile.ConfigFile{
104-
Filename: configPath,
105-
Features: map[string]string{
106-
"configured-catalogs": "enabled",
107-
},
108-
}
109-
110-
// Test disabling configured-catalogs feature
111-
err := disableFeature(configFile, "configured-catalogs")
112-
require.NoError(t, err)
113-
114-
// Verify feature was disabled
115-
enabled := isFeatureEnabled(configFile, "configured-catalogs")
116-
assert.False(t, enabled, "configured-catalogs feature should be disabled")
117-
}
118-
119-
func TestListFeatures(t *testing.T) {
120-
// Create config with mixed features
121-
configFile := &configfile.ConfigFile{
122-
Features: map[string]string{
123-
"configured-catalogs": "enabled",
124-
"other-feature": "disabled",
125-
},
126-
}
127-
128-
// Test listing features
129-
features := listFeatures(configFile)
130-
131-
// Should contain our feature with correct status
132-
assert.Contains(t, features, "configured-catalogs")
133-
assert.Contains(t, features, "other-feature")
134-
assert.Equal(t, "enabled", features["configured-catalogs"])
135-
assert.Equal(t, "disabled", features["other-feature"])
136-
}
137-
138-
func TestInvalidFeature(t *testing.T) {
139-
configFile := &configfile.ConfigFile{
140-
Features: make(map[string]string),
141-
}
142-
143-
// Test enabling invalid feature
144-
err := enableFeature(configFile, "invalid-feature")
145-
require.Error(t, err, "should reject invalid feature names")
146-
assert.Contains(t, err.Error(), "unknown feature")
147-
}
148-
149-
// Feature management functions that need to be implemented
150-
func enableFeature(configFile *configfile.ConfigFile, feature string) error {
151-
// Validate feature name
152-
if !isKnownFeature(feature) {
153-
return &featureError{feature: feature, message: "unknown feature"}
154-
}
155-
156-
// Enable the feature
157-
if configFile.Features == nil {
158-
configFile.Features = make(map[string]string)
159-
}
160-
configFile.Features[feature] = "enabled"
161-
162-
// Save config file
163-
return configFile.Save()
164-
}
165-
166-
func disableFeature(configFile *configfile.ConfigFile, feature string) error {
167-
// Validate feature name
168-
if !isKnownFeature(feature) {
169-
return &featureError{feature: feature, message: "unknown feature"}
170-
}
171-
172-
// Disable the feature
173-
if configFile.Features == nil {
174-
configFile.Features = make(map[string]string)
175-
}
176-
configFile.Features[feature] = "disabled"
177-
178-
// Save config file
179-
return configFile.Save()
180-
}
181-
182-
func listFeatures(configFile *configfile.ConfigFile) map[string]string {
183-
if configFile.Features == nil {
184-
return make(map[string]string)
185-
}
186-
187-
// Return copy of features map
188-
result := make(map[string]string)
189-
for k, v := range configFile.Features {
190-
result[k] = v
191-
}
192-
return result
193-
}
194-
195-
func isFeatureEnabled(configFile *configfile.ConfigFile, _ string) bool {
196-
if configFile.Features == nil {
197-
return false
198-
}
199-
200-
value, exists := configFile.Features["configured-catalogs"]
201-
if !exists {
202-
return false
203-
}
204-
205-
// Handle both boolean string values and "enabled"/"disabled" strings
206-
if value == "enabled" {
207-
return true
208-
}
209-
if value == "disabled" {
210-
return false
211-
}
212-
213-
// Fallback to parsing as boolean
214-
enabled, err := strconv.ParseBool(value)
215-
return err == nil && enabled
216-
}
217-
218-
// Feature error type
219-
type featureError struct {
220-
feature string
221-
message string
222-
}
70+
func TestIsKnownFeature(t *testing.T) {
71+
// Test valid features
72+
assert.True(t, isKnownFeature("oauth-interceptor"))
73+
assert.True(t, isKnownFeature("dynamic-tools"))
22374

224-
func (e *featureError) Error() string {
225-
return e.message + ": " + e.feature
75+
// Test invalid features
76+
assert.False(t, isKnownFeature("invalid-feature"))
77+
assert.False(t, isKnownFeature("configured-catalogs")) // No longer supported
78+
assert.False(t, isKnownFeature(""))
22679
}

0 commit comments

Comments
 (0)