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
38func DockerMCPAvailabilityCached() (available bool, known bool) {
39 dockerMCPAvailabilityCache.mu.Lock()
40 defer dockerMCPAvailabilityCache.mu.Unlock()
41
42 if !dockerMCPAvailabilityCache.known {
43 return false, false
44 }
45 if time.Since(dockerMCPAvailabilityCache.checkedAt) > dockerMCPAvailabilityTTL {
46 return dockerMCPAvailabilityCache.available, false
47 }
48 return dockerMCPAvailabilityCache.available, true
49}
50
51func RefreshDockerMCPAvailability() bool {
52 available := IsDockerMCPAvailable()
53 dockerMCPAvailabilityCache.mu.Lock()
54 dockerMCPAvailabilityCache.available = available
55 dockerMCPAvailabilityCache.checkedAt = time.Now()
56 dockerMCPAvailabilityCache.known = true
57 dockerMCPAvailabilityCache.mu.Unlock()
58 return available
59}
60
61// IsDockerMCPEnabled checks if Docker MCP is already configured.
62func (c *Config) IsDockerMCPEnabled() bool {
63 if c.MCP == nil {
64 return false
65 }
66 _, exists := c.MCP[DockerMCPName]
67 return exists
68}
69
70func DockerMCPConfig() MCPConfig {
71 return MCPConfig{
72 Type: MCPStdio,
73 Command: "docker",
74 Args: []string{"mcp", "gateway", "run"},
75 Disabled: false,
76 }
77}
78
79func (s *ConfigStore) PrepareDockerMCPConfig() (MCPConfig, error) {
80 if !IsDockerMCPAvailable() {
81 return MCPConfig{}, fmt.Errorf("docker mcp is not available, please ensure docker is installed and 'docker mcp version' succeeds")
82 }
83
84 mcpConfig := DockerMCPConfig()
85 if s.config.MCP == nil {
86 s.config.MCP = make(map[string]MCPConfig)
87 }
88 s.config.MCP[DockerMCPName] = mcpConfig
89 return mcpConfig, nil
90}
91
92func (s *ConfigStore) PersistDockerMCPConfig(mcpConfig MCPConfig) error {
93 if err := s.SetConfigField(ScopeGlobal, "mcp."+DockerMCPName, mcpConfig); err != nil {
94 return fmt.Errorf("failed to persist docker mcp configuration: %w", err)
95 }
96 return nil
97}
98
99// EnableDockerMCP adds Docker MCP configuration and persists it.
100func (s *ConfigStore) EnableDockerMCP() error {
101 mcpConfig, err := s.PrepareDockerMCPConfig()
102 if err != nil {
103 return err
104 }
105 if err := s.PersistDockerMCPConfig(mcpConfig); err != nil {
106 return err
107 }
108 return nil
109}
110
111// DisableDockerMCP removes Docker MCP configuration and persists the change.
112func (s *ConfigStore) DisableDockerMCP() error {
113 if s.config.MCP == nil {
114 return nil
115 }
116
117 // Remove from in-memory config.
118 delete(s.config.MCP, DockerMCPName)
119
120 // Persist the updated MCP map to the config file.
121 if err := s.SetConfigField(ScopeGlobal, "mcp", s.config.MCP); err != nil {
122 return fmt.Errorf("failed to persist docker mcp removal: %w", err)
123 }
124
125 return nil
126}