diff --git a/internal/config/docker_mcp.go b/internal/config/docker_mcp.go index 7bd1d6eadd202fb2f5a10476f8da062d0b57b64b..01445a9ae7583b8ac489542d08d745e49907521a 100644 --- a/internal/config/docker_mcp.go +++ b/internal/config/docker_mcp.go @@ -34,30 +34,44 @@ func (c *Config) IsDockerMCPEnabled() bool { return exists } -// EnableDockerMCP adds Docker MCP configuration and persists it. -func (s *ConfigStore) EnableDockerMCP() error { - if !IsDockerMCPAvailable() { - return fmt.Errorf("docker mcp is not available, please ensure docker is installed and 'docker mcp version' succeeds") - } - - mcpConfig := MCPConfig{ +func DockerMCPConfig() MCPConfig { + return MCPConfig{ Type: MCPStdio, Command: "docker", Args: []string{"mcp", "gateway", "run"}, Disabled: false, } +} + +func (s *ConfigStore) PrepareDockerMCPConfig() (MCPConfig, error) { + if !IsDockerMCPAvailable() { + return MCPConfig{}, fmt.Errorf("docker mcp is not available, please ensure docker is installed and 'docker mcp version' succeeds") + } - // Add to in-memory config. + mcpConfig := DockerMCPConfig() if s.config.MCP == nil { s.config.MCP = make(map[string]MCPConfig) } s.config.MCP[DockerMCPName] = mcpConfig + return mcpConfig, nil +} - // Persist to config file. +func (s *ConfigStore) PersistDockerMCPConfig(mcpConfig MCPConfig) error { if err := s.SetConfigField(ScopeGlobal, "mcp."+DockerMCPName, mcpConfig); err != nil { return fmt.Errorf("failed to persist docker mcp configuration: %w", err) } + return nil +} +// EnableDockerMCP adds Docker MCP configuration and persists it. +func (s *ConfigStore) EnableDockerMCP() error { + mcpConfig, err := s.PrepareDockerMCPConfig() + if err != nil { + return err + } + if err := s.PersistDockerMCPConfig(mcpConfig); err != nil { + return err + } return nil } diff --git a/internal/ui/model/ui.go b/internal/ui/model/ui.go index 7e7c36348337bf0d8299fa9d9eba7e52176be284..e87bc148c6a8e0286ed138cbd426697ac039a5d5 100644 --- a/internal/ui/model/ui.go +++ b/internal/ui/model/ui.go @@ -3465,16 +3465,26 @@ func (m *UI) copyChatHighlight() tea.Cmd { func (m *UI) enableDockerMCP() tea.Msg { store := m.com.Store() - if err := store.EnableDockerMCP(); err != nil { + // Stage Docker MCP in memory first so startup and persistence can be atomic. + mcpConfig, err := store.PrepareDockerMCPConfig() + if err != nil { return util.ReportError(err)() } - // Initialize the Docker MCP client immediately. ctx := context.Background() if err := mcp.InitializeSingle(ctx, config.DockerMCPName, store); err != nil { + // Roll back in-memory state when startup fails. + delete(store.Config().MCP, config.DockerMCPName) return util.ReportError(fmt.Errorf("docker MCP enabled but failed to start: %w", err))() } + if err := store.PersistDockerMCPConfig(mcpConfig); err != nil { + // Roll back runtime and in-memory state if persistence fails. + _ = mcp.DisableSingle(store, config.DockerMCPName) + delete(store.Config().MCP, config.DockerMCPName) + return util.ReportError(fmt.Errorf("docker MCP started but failed to persist configuration: %w", err))() + } + return util.NewInfoMsg("Docker MCP enabled and started successfully") }