on main help sign in
fix(mcp/docker): only write config after docker startup succeeds
351e562Christian Rocha
internal/config/docker_mcp.go+23−9
@@ -34,30 +34,44 @@ func (c *Config) IsDockerMCPEnabled() bool {
34 return exists
34 return exists
35}
35}
36
36
37−// EnableDockerMCP adds Docker MCP configuration and persists it.
37+func DockerMCPConfig() MCPConfig {
38−func (s *ConfigStore) EnableDockerMCP() error {
39− if !IsDockerMCPAvailable() {
40− return fmt.Errorf("docker mcp is not available, please ensure docker is installed and 'docker mcp version' succeeds")
41− }
42−
43− mcpConfig := MCPConfig{
38+ return MCPConfig{
44 Type: MCPStdio,
39 Type: MCPStdio,
45 Command: "docker",
40 Command: "docker",
46 Args: []string{"mcp", "gateway", "run"},
41 Args: []string{"mcp", "gateway", "run"},
47 Disabled: false,
42 Disabled: false,
48 }
43 }
44+}
45+
46+func (s *ConfigStore) PrepareDockerMCPConfig() (MCPConfig, error) {
47+ if !IsDockerMCPAvailable() {
48+ return MCPConfig{}, fmt.Errorf("docker mcp is not available, please ensure docker is installed and 'docker mcp version' succeeds")
49+ }
49
50
50− // Add to in-memory config.
51+ mcpConfig := DockerMCPConfig()
51 if s.config.MCP == nil {
52 if s.config.MCP == nil {
52 s.config.MCP = make(map[string]MCPConfig)
53 s.config.MCP = make(map[string]MCPConfig)
53 }
54 }
54 s.config.MCP[DockerMCPName] = mcpConfig
55 s.config.MCP[DockerMCPName] = mcpConfig
56+ return mcpConfig, nil
57+}
55
58
56− // Persist to config file.
59+func (s *ConfigStore) PersistDockerMCPConfig(mcpConfig MCPConfig) error {
57 if err := s.SetConfigField(ScopeGlobal, "mcp."+DockerMCPName, mcpConfig); err != nil {
60 if err := s.SetConfigField(ScopeGlobal, "mcp."+DockerMCPName, mcpConfig); err != nil {
58 return fmt.Errorf("failed to persist docker mcp configuration: %w", err)
61 return fmt.Errorf("failed to persist docker mcp configuration: %w", err)
59 }
62 }
63+ return nil
64+}
60
65
66+// EnableDockerMCP adds Docker MCP configuration and persists it.
67+func (s *ConfigStore) EnableDockerMCP() error {
68+ mcpConfig, err := s.PrepareDockerMCPConfig()
69+ if err != nil {
70+ return err
71+ }
72+ if err := s.PersistDockerMCPConfig(mcpConfig); err != nil {
73+ return err
74+ }
61 return nil
75 return nil
62}
76}
63
77
internal/ui/model/ui.go+12−2
@@ -3465,16 +3465,26 @@ func (m *UI) copyChatHighlight() tea.Cmd {
3465
3465
3466func (m *UI) enableDockerMCP() tea.Msg {
3466func (m *UI) enableDockerMCP() tea.Msg {
3467 store := m.com.Store()
3467 store := m.com.Store()
3468− if err := store.EnableDockerMCP(); err != nil {
3468+ // Stage Docker MCP in memory first so startup and persistence can be atomic.
3469+ mcpConfig, err := store.PrepareDockerMCPConfig()
3470+ if err != nil {
3469 return util.ReportError(err)()
3471 return util.ReportError(err)()
3470 }
3472 }
3471
3473
3472− // Initialize the Docker MCP client immediately.
3473 ctx := context.Background()
3474 ctx := context.Background()
3474 if err := mcp.InitializeSingle(ctx, config.DockerMCPName, store); err != nil {
3475 if err := mcp.InitializeSingle(ctx, config.DockerMCPName, store); err != nil {
3476+ // Roll back in-memory state when startup fails.
3477+ delete(store.Config().MCP, config.DockerMCPName)
3475 return util.ReportError(fmt.Errorf("docker MCP enabled but failed to start: %w", err))()
3478 return util.ReportError(fmt.Errorf("docker MCP enabled but failed to start: %w", err))()
3476 }
3479 }
3477
3480
3481+ if err := store.PersistDockerMCPConfig(mcpConfig); err != nil {
3482+ // Roll back runtime and in-memory state if persistence fails.
3483+ _ = mcp.DisableSingle(store, config.DockerMCPName)
3484+ delete(store.Config().MCP, config.DockerMCPName)
3485+ return util.ReportError(fmt.Errorf("docker MCP started but failed to persist configuration: %w", err))()
3486+ }
3487+
3478 return util.NewInfoMsg("Docker MCP enabled and started successfully")
3488 return util.NewInfoMsg("Docker MCP enabled and started successfully")
3479}
3489}
3480
3490