Skip to content

Commit 8d05bc1

Browse files
fix: prevent unnecessary MCP server refresh on settings save (RooCodeInc#6772) (RooCodeInc#6779)
1 parent c99ccf0 commit 8d05bc1

File tree

2 files changed

+159
-4
lines changed

2 files changed

+159
-4
lines changed

src/core/webview/__tests__/webviewMessageHandler.spec.ts

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const mockClineProvider = {
3535
getCurrentCline: vi.fn(),
3636
getTaskWithId: vi.fn(),
3737
initClineWithHistoryItem: vi.fn(),
38+
getMcpHub: vi.fn(),
3839
} as unknown as ClineProvider
3940

4041
import { t } from "../../../i18n"
@@ -576,3 +577,150 @@ describe("webviewMessageHandler - message dialog preferences", () => {
576577
})
577578
})
578579
})
580+
581+
describe("webviewMessageHandler - mcpEnabled", () => {
582+
let mockMcpHub: any
583+
584+
beforeEach(() => {
585+
vi.clearAllMocks()
586+
587+
// Create a mock McpHub instance
588+
mockMcpHub = {
589+
handleMcpEnabledChange: vi.fn().mockResolvedValue(undefined),
590+
}
591+
592+
// Mock the getMcpHub method to return our mock McpHub
593+
mockClineProvider.getMcpHub = vi.fn().mockReturnValue(mockMcpHub)
594+
595+
// Reset the contextProxy getValue mock
596+
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(undefined)
597+
})
598+
599+
it("should not refresh MCP servers when value does not change (true to true)", async () => {
600+
// Setup: mcpEnabled is already true
601+
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)
602+
603+
// Act: Send mcpEnabled message with same value
604+
await webviewMessageHandler(mockClineProvider, {
605+
type: "mcpEnabled",
606+
bool: true,
607+
})
608+
609+
// Assert: handleMcpEnabledChange should not be called
610+
expect(mockMcpHub.handleMcpEnabledChange).not.toHaveBeenCalled()
611+
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", true)
612+
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
613+
})
614+
615+
it("should not refresh MCP servers when value does not change (false to false)", async () => {
616+
// Setup: mcpEnabled is already false
617+
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false)
618+
619+
// Act: Send mcpEnabled message with same value
620+
await webviewMessageHandler(mockClineProvider, {
621+
type: "mcpEnabled",
622+
bool: false,
623+
})
624+
625+
// Assert: handleMcpEnabledChange should not be called
626+
expect(mockMcpHub.handleMcpEnabledChange).not.toHaveBeenCalled()
627+
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", false)
628+
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
629+
})
630+
631+
it("should refresh MCP servers when value changes from true to false", async () => {
632+
// Setup: mcpEnabled is true
633+
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)
634+
635+
// Act: Send mcpEnabled message with false
636+
await webviewMessageHandler(mockClineProvider, {
637+
type: "mcpEnabled",
638+
bool: false,
639+
})
640+
641+
// Assert: handleMcpEnabledChange should be called
642+
expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledWith(false)
643+
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", false)
644+
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
645+
})
646+
647+
it("should refresh MCP servers when value changes from false to true", async () => {
648+
// Setup: mcpEnabled is false
649+
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false)
650+
651+
// Act: Send mcpEnabled message with true
652+
await webviewMessageHandler(mockClineProvider, {
653+
type: "mcpEnabled",
654+
bool: true,
655+
})
656+
657+
// Assert: handleMcpEnabledChange should be called
658+
expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledWith(true)
659+
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", true)
660+
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
661+
})
662+
663+
it("should handle undefined values with defaults correctly", async () => {
664+
// Setup: mcpEnabled is undefined (defaults to true)
665+
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(undefined)
666+
667+
// Act: Send mcpEnabled message with undefined (defaults to true)
668+
await webviewMessageHandler(mockClineProvider, {
669+
type: "mcpEnabled",
670+
bool: undefined,
671+
})
672+
673+
// Assert: Should use default value (true) and not trigger refresh since both are true
674+
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", true)
675+
expect(mockMcpHub.handleMcpEnabledChange).not.toHaveBeenCalled()
676+
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
677+
})
678+
679+
it("should handle when mcpEnabled changes from undefined to false", async () => {
680+
// Setup: mcpEnabled is undefined (defaults to true)
681+
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(undefined)
682+
683+
// Act: Send mcpEnabled message with false
684+
await webviewMessageHandler(mockClineProvider, {
685+
type: "mcpEnabled",
686+
bool: false,
687+
})
688+
689+
// Assert: Should trigger refresh since undefined defaults to true and we're changing to false
690+
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", false)
691+
expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledWith(false)
692+
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
693+
})
694+
695+
it("should not call handleMcpEnabledChange when McpHub is not available", async () => {
696+
// Setup: No McpHub instance available
697+
mockClineProvider.getMcpHub = vi.fn().mockReturnValue(null)
698+
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)
699+
700+
// Act: Send mcpEnabled message with false
701+
await webviewMessageHandler(mockClineProvider, {
702+
type: "mcpEnabled",
703+
bool: false,
704+
})
705+
706+
// Assert: State should be updated but handleMcpEnabledChange should not be called
707+
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", false)
708+
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
709+
// No error should be thrown
710+
})
711+
712+
it("should always update state even when value doesn't change", async () => {
713+
// Setup: mcpEnabled is true
714+
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)
715+
716+
// Act: Send mcpEnabled message with same value
717+
await webviewMessageHandler(mockClineProvider, {
718+
type: "mcpEnabled",
719+
bool: true,
720+
})
721+
722+
// Assert: State should still be updated to ensure consistency
723+
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", true)
724+
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
725+
})
726+
})

src/core/webview/webviewMessageHandler.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -900,12 +900,19 @@ export const webviewMessageHandler = async (
900900
}
901901
case "mcpEnabled":
902902
const mcpEnabled = message.bool ?? true
903+
const currentMcpEnabled = getGlobalState("mcpEnabled") ?? true
904+
905+
// Always update the state to ensure consistency
903906
await updateGlobalState("mcpEnabled", mcpEnabled)
904907

905-
// Delegate MCP enable/disable logic to McpHub
906-
const mcpHubInstance = provider.getMcpHub()
907-
if (mcpHubInstance) {
908-
await mcpHubInstance.handleMcpEnabledChange(mcpEnabled)
908+
// Only refresh MCP connections if the value actually changed
909+
// This prevents expensive MCP server refresh operations when saving unrelated settings
910+
if (currentMcpEnabled !== mcpEnabled) {
911+
// Delegate MCP enable/disable logic to McpHub
912+
const mcpHubInstance = provider.getMcpHub()
913+
if (mcpHubInstance) {
914+
await mcpHubInstance.handleMcpEnabledChange(mcpEnabled)
915+
}
909916
}
910917

911918
await provider.postStateToWebview()

0 commit comments

Comments
 (0)