docker_mcp.go

  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}