fix: keep MCP activation flags consistent across start and stop

This commit is contained in:
Ed1s0nZ committed 2026-10-09 14:15:24 +08:00
1 parent fab7e60318
commit ff50af028f
3 files changed
+26

No files matched your search

@@ -42,3 +42,7 @@ Implemented strict recovery decoding in recovery_evidence.go, with exact field n
MCP edits distinguish omitted activation fields from explicit booleans. Omitted fields preserve the manager state; Start/Stop and persisted disabled are synchronized so restart retains the requested state. Existing environment snapshot expansion is preserved.
Tests added for malformed build/rollback journals, all independent review counterexamples, valid partial binary rename and final cleanup retries, and MCP stop/edit/start plus config reload. Targeted reproductions pass; original full Go, build, five-package race, and 277 JS checks passed before final evidence guard refinements. Final exact-patch validation follows in F5. No production external service was contacted.
### F4 follow-up: activation round-trip consistency
Final GET-to-edit inspection found that manager Start/Stop retained a stale disabled flag even after changing ExternalMCPEnable. Synchronize these two existing fields in the manager as well as persistence, and test the actual GET payload after Start followed by the UI-shaped edit. Maintainability assessment: external_manager.go is 1665 lines (high risk); this narrow fix adds only three assignments beside existing activation assignments, without new control flow or broad refactoring. The additional handler/MCP race suites validate this consistency change.
@@ -74,6 +74,25 @@ func TestMCPEditPreservesStoppedState(t *testing.T) {
if !h.manager.GetConfigs()["stopped"].ExternalMCPEnable {
t.Fatal("explicit Start stayed disabled")
}
response := request("GET", "/api/external-mcp/stopped", nil)
var server ExternalMCPResponse
if err := json.Unmarshal(response.Body.Bytes(), &server); err != nil {
t.Fatal(err)
}
if server.Config.Disabled {
t.Fatal("Start left a stale disabled field in the edit response")
}
// Round-trip the actual GET payload just as the UI does, removing enable.
encoded, _ := json.Marshal(server.Config)
var edit map[string]any
_ = json.Unmarshal(encoded, &edit)
delete(edit, "external_mcp_enable")
edit["description"] = "after explicit start"
body, _ = json.Marshal(map[string]any{"config": edit})
request("PUT", "/api/external-mcp/stopped", body)
if !h.manager.GetConfigs()["stopped"].ExternalMCPEnable {
t.Fatal("edit after Start stopped the MCP")
}
persisted, err = config.Load(path)
if err != nil {
t.Fatal(err)
+3
View File
@@ -337,6 +337,7 @@ func (m *ExternalMCPManager) startClient(name string, autoReconnect bool) error
if !autoReconnect {
m.mu.Lock()
serverCfg.ExternalMCPEnable = true
serverCfg.Disabled = false
m.configs[name] = serverCfg
m.mu.Unlock()
}
@@ -362,6 +363,7 @@ func (m *ExternalMCPManager) startClient(name string, autoReconnect bool) error
// 更新配置为启用
m.mu.Lock()
serverCfg.ExternalMCPEnable = true
serverCfg.Disabled = false
m.configs[name] = serverCfg
// 清除之前的错误信息(重新启动时)
delete(m.errors, name)
@@ -443,6 +445,7 @@ func (m *ExternalMCPManager) StopClient(name string) error {
// 更新配置为禁用
serverCfg.ExternalMCPEnable = false
serverCfg.Disabled = true
m.configs[name] = serverCfg
m.clearReconnectState(name)