1package config
2
3import (
4 "context"
5 "fmt"
6 "os/exec"
7 "sync"
8 "time"
9)
10
11var dockerMCPVersionRunner = func(ctx context.Context) error {
12 cmd := exec.CommandContext(ctx, "docker", "mcp", "version")
13 return cmd.Run()
14}
15
16const dockerMCPAvailabilityTTL = 10 * time.Second
17
18var dockerMCPAvailabilityCache struct {
19 mu sync.Mutex
20 available bool
21 checkedAt time.Time
22 known bool
23}
24
25// DockerMCPName is the name of the Docker MCP configuration.
26const DockerMCPName = "docker"
27
28// IsDockerMCPAvailable checks if Docker MCP is available by running
29// 'docker mcp version'.
30func IsDockerMCPAvailable() bool {
31 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
32 defer cancel()
33
34 err := dockerMCPVersionRunner(ctx)
35 return err == nil
36}
37
38// DockerMCPAvailabilityCached returns the cached Docker MCP availability and
39// whether the cached value is still fresh.
40func DockerMCPAvailabilityCached() (available bool, known bool) {
41 dockerMCPAvailabilityCache.mu.Lock()
42 defer dockerMCPAvailabilityCache.mu.Unlock()
43
44 if !dockerMCPAvailabilityCache.known {
45 return false, false
46 }
47 if time.Since(dockerMCPAvailabilityCache.checkedAt) > dockerMCPAvailabilityTTL {
48 return dockerMCPAvailabilityCache.available, false
49 }
50 return dockerMCPAvailabilityCache.available, true
51}
52
53// RefreshDockerMCPAvailability refreshes and caches Docker MCP availability.
54func RefreshDockerMCPAvailability() bool {
55 available := IsDockerMCPAvailable()
56 dockerMCPAvailabilityCache.mu.Lock()
57 dockerMCPAvailabilityCache.available = available
58 dockerMCPAvailabilityCache.checkedAt = time.Now()
59 dockerMCPAvailabilityCache.known = true
60 dockerMCPAvailabilityCache.mu.Unlock()
61 return available
62}
63
64// IsDockerMCPEnabled checks if Docker MCP is already configured.
65func (c *Config) IsDockerMCPEnabled() bool {
66 if c.MCP == nil {
67 return false
68 }
69 _, exists := c.MCP[DockerMCPName]
70 return exists
71}
72
73// DockerMCPConfig returns the default Docker MCP stdio configuration.
74func DockerMCPConfig() MCPConfig {
75 return MCPConfig{
76 Type: MCPStdio,
77 Command: "docker",
78 Args: []string{"mcp", "gateway", "run"},
79 Disabled: false,
80 }
81}
82
83// PrepareDockerMCPConfig validates Docker MCP availability and stages the
84// Docker MCP configuration in memory.
85func (s *ConfigStore) PrepareDockerMCPConfig() (MCPConfig, error) {
86 if !IsDockerMCPAvailable() {
87 return MCPConfig{}, fmt.Errorf("docker mcp is not available, please ensure docker is installed and 'docker mcp version' succeeds")
88 }
89
90 mcpConfig := DockerMCPConfig()
91 if s.config.MCP == nil {
92 s.config.MCP = make(map[string]MCPConfig)
93 }
94 s.config.MCP[DockerMCPName] = mcpConfig
95 return mcpConfig, nil
96}
97
98// PersistDockerMCPConfig persists a previously prepared Docker MCP
99// configuration to the global config file.
100func (s *ConfigStore) PersistDockerMCPConfig(mcpConfig MCPConfig) error {
101 if err := s.SetConfigField(ScopeGlobal, "mcp."+DockerMCPName, mcpConfig); err != nil {
102 return fmt.Errorf("failed to persist docker mcp configuration: %w", err)
103 }
104 return nil
105}
106
107// EnableDockerMCP adds Docker MCP configuration and persists it.
108func (s *ConfigStore) EnableDockerMCP() error {
109 mcpConfig, err := s.PrepareDockerMCPConfig()
110 if err != nil {
111 return err
112 }
113 if err := s.PersistDockerMCPConfig(mcpConfig); err != nil {
114 return err
115 }
116 return nil
117}
118
119// DisableDockerMCP removes Docker MCP configuration and persists the change.
120func (s *ConfigStore) DisableDockerMCP() error {
121 if s.config.MCP == nil {
122 return nil
123 }
124
125 // Remove from in-memory config.
126 delete(s.config.MCP, DockerMCPName)
127
128 // Persist the updated MCP map to the config file.
129 if err := s.SetConfigField(ScopeGlobal, "mcp", s.config.MCP); err != nil {
130 return fmt.Errorf("failed to persist docker mcp removal: %w", err)
131 }
132
133 return nil
134}