Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 14 additions & 110 deletions src/core/webview/__tests__/webviewMessageHandler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ const mockClineProvider = {
getCurrentCline: vi.fn(),
getTaskWithId: vi.fn(),
initClineWithHistoryItem: vi.fn(),
getMcpHub: vi.fn(),
} as unknown as ClineProvider

import { t } from "../../../i18n"
Expand Down Expand Up @@ -589,138 +588,43 @@ describe("webviewMessageHandler - mcpEnabled", () => {
handleMcpEnabledChange: vi.fn().mockResolvedValue(undefined),
}

// Mock the getMcpHub method to return our mock McpHub
mockClineProvider.getMcpHub = vi.fn().mockReturnValue(mockMcpHub)

// Reset the contextProxy getValue mock
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(undefined)
// Ensure provider exposes getMcpHub and returns our mock
;(mockClineProvider as any).getMcpHub = vi.fn().mockReturnValue(mockMcpHub)
})

it("should not refresh MCP servers when value does not change (true to true)", async () => {
// Setup: mcpEnabled is already true
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)

// Act: Send mcpEnabled message with same value
it("delegates enable=true to McpHub and posts updated state", async () => {
await webviewMessageHandler(mockClineProvider, {
type: "mcpEnabled",
bool: true,
})

// Assert: handleMcpEnabledChange should not be called
expect(mockMcpHub.handleMcpEnabledChange).not.toHaveBeenCalled()
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", true)
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
})

it("should not refresh MCP servers when value does not change (false to false)", async () => {
// Setup: mcpEnabled is already false
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false)

// Act: Send mcpEnabled message with same value
await webviewMessageHandler(mockClineProvider, {
type: "mcpEnabled",
bool: false,
})

// Assert: handleMcpEnabledChange should not be called
expect(mockMcpHub.handleMcpEnabledChange).not.toHaveBeenCalled()
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", false)
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
})

it("should refresh MCP servers when value changes from true to false", async () => {
// Setup: mcpEnabled is true
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)

// Act: Send mcpEnabled message with false
await webviewMessageHandler(mockClineProvider, {
type: "mcpEnabled",
bool: false,
})

// Assert: handleMcpEnabledChange should be called
expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledWith(false)
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", false)
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
})

it("should refresh MCP servers when value changes from false to true", async () => {
// Setup: mcpEnabled is false
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(false)

// Act: Send mcpEnabled message with true
await webviewMessageHandler(mockClineProvider, {
type: "mcpEnabled",
bool: true,
})

// Assert: handleMcpEnabledChange should be called
expect((mockClineProvider as any).getMcpHub).toHaveBeenCalledTimes(1)
expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledTimes(1)
expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledWith(true)
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", true)
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
})

it("should handle undefined values with defaults correctly", async () => {
// Setup: mcpEnabled is undefined (defaults to true)
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(undefined)

// Act: Send mcpEnabled message with undefined (defaults to true)
await webviewMessageHandler(mockClineProvider, {
type: "mcpEnabled",
bool: undefined,
})

// Assert: Should use default value (true) and not trigger refresh since both are true
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", true)
expect(mockMcpHub.handleMcpEnabledChange).not.toHaveBeenCalled()
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
expect(mockClineProvider.postStateToWebview).toHaveBeenCalledTimes(1)
})

it("should handle when mcpEnabled changes from undefined to false", async () => {
// Setup: mcpEnabled is undefined (defaults to true)
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(undefined)

// Act: Send mcpEnabled message with false
it("delegates enable=false to McpHub and posts updated state", async () => {
await webviewMessageHandler(mockClineProvider, {
type: "mcpEnabled",
bool: false,
})

// Assert: Should trigger refresh since undefined defaults to true and we're changing to false
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", false)
expect((mockClineProvider as any).getMcpHub).toHaveBeenCalledTimes(1)
expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledTimes(1)
expect(mockMcpHub.handleMcpEnabledChange).toHaveBeenCalledWith(false)
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
})

it("should not call handleMcpEnabledChange when McpHub is not available", async () => {
// Setup: No McpHub instance available
mockClineProvider.getMcpHub = vi.fn().mockReturnValue(null)
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)

// Act: Send mcpEnabled message with false
await webviewMessageHandler(mockClineProvider, {
type: "mcpEnabled",
bool: false,
})

// Assert: State should be updated but handleMcpEnabledChange should not be called
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", false)
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
// No error should be thrown
expect(mockClineProvider.postStateToWebview).toHaveBeenCalledTimes(1)
})

it("should always update state even when value doesn't change", async () => {
// Setup: mcpEnabled is true
vi.mocked(mockClineProvider.contextProxy.getValue).mockReturnValue(true)
it("handles missing McpHub instance gracefully and still posts state", async () => {
;(mockClineProvider as any).getMcpHub = vi.fn().mockReturnValue(undefined)

// Act: Send mcpEnabled message with same value
await webviewMessageHandler(mockClineProvider, {
type: "mcpEnabled",
bool: true,
})

// Assert: State should still be updated to ensure consistency
expect(mockClineProvider.contextProxy.setValue).toHaveBeenCalledWith("mcpEnabled", true)
expect(mockClineProvider.postStateToWebview).toHaveBeenCalled()
expect((mockClineProvider as any).getMcpHub).toHaveBeenCalledTimes(1)
expect(mockClineProvider.postStateToWebview).toHaveBeenCalledTimes(1)
})
})
15 changes: 4 additions & 11 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -900,19 +900,12 @@ export const webviewMessageHandler = async (
}
case "mcpEnabled":
const mcpEnabled = message.bool ?? true
const currentMcpEnabled = getGlobalState("mcpEnabled") ?? true

// Always update the state to ensure consistency
await updateGlobalState("mcpEnabled", mcpEnabled)

// Only refresh MCP connections if the value actually changed
// This prevents expensive MCP server refresh operations when saving unrelated settings
if (currentMcpEnabled !== mcpEnabled) {
// Delegate MCP enable/disable logic to McpHub
const mcpHubInstance = provider.getMcpHub()
if (mcpHubInstance) {
await mcpHubInstance.handleMcpEnabledChange(mcpEnabled)
}
// Delegate MCP enable/disable logic to McpHub
const mcpHubInstance = provider.getMcpHub()
if (mcpHubInstance) {
await mcpHubInstance.handleMcpEnabledChange(mcpEnabled)
}

await provider.postStateToWebview()
Expand Down
14 changes: 0 additions & 14 deletions src/services/mcp/McpHub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1212,7 +1212,6 @@ export class McpHub {

public async refreshAllConnections(): Promise<void> {
if (this.isConnecting) {
vscode.window.showInformationMessage(t("mcp:info.already_refreshing"))
return
}

Expand All @@ -1234,7 +1233,6 @@ export class McpHub {
}

this.isConnecting = true
vscode.window.showInformationMessage(t("mcp:info.refreshing_all"))

try {
const globalPath = await this.getMcpSettingsFilePath()
Expand All @@ -1244,11 +1242,6 @@ export class McpHub {
const globalConfig = JSON.parse(globalContent)
globalServers = globalConfig.mcpServers || {}
const globalServerNames = Object.keys(globalServers)
vscode.window.showInformationMessage(
t("mcp:info.global_servers_active", {
mcpServers: `${globalServerNames.join(", ") || "none"}`,
}),
)
} catch (error) {
console.log("Error reading global MCP config:", error)
}
Expand All @@ -1261,11 +1254,6 @@ export class McpHub {
const projectConfig = JSON.parse(projectContent)
projectServers = projectConfig.mcpServers || {}
const projectServerNames = Object.keys(projectServers)
vscode.window.showInformationMessage(
t("mcp:info.project_servers_active", {
mcpServers: `${projectServerNames.join(", ") || "none"}`,
}),
)
} catch (error) {
console.log("Error reading project MCP config:", error)
}
Expand All @@ -1285,8 +1273,6 @@ export class McpHub {
await delay(100)

await this.notifyWebviewOfServerChanges()

vscode.window.showInformationMessage(t("mcp:info.all_refreshed"))
} catch (error) {
this.showErrorMessage("Failed to refresh MCP servers", error)
} finally {
Expand Down
Loading