diff --git a/src/core/auto-approval/__tests__/index.spec.ts b/src/core/auto-approval/__tests__/index.spec.ts new file mode 100644 index 0000000000..9243d1178e --- /dev/null +++ b/src/core/auto-approval/__tests__/index.spec.ts @@ -0,0 +1,214 @@ +import { describe, it, expect, vi } from "vitest" +import { checkAutoApproval } from "../index" +import type { ExtensionState } from "../../../shared/ExtensionMessage" +import type { McpServerUse } from "@roo-code/types" + +describe("checkAutoApproval", () => { + describe("MCP auto-approval", () => { + it("should approve MCP tool when alwaysAllowMcp is enabled", async () => { + const mcpServerUse: McpServerUse = { + type: "use_mcp_tool", + serverName: "test-server", + toolName: "test-tool", + } + + const state: Partial = { + autoApprovalEnabled: true, + alwaysAllowMcp: true, + mcpServers: [ + { + name: "test-server", + config: "test-config", + status: "connected", + tools: [ + { + name: "test-tool", + alwaysAllow: false, // Tool does NOT have alwaysAllow flag + }, + ], + }, + ] as any, + } + + const result = await checkAutoApproval({ + state: state as ExtensionState, + ask: "use_mcp_server", + text: JSON.stringify(mcpServerUse), + }) + + // Should approve because alwaysAllowMcp is true, regardless of tool's alwaysAllow flag + expect(result).toEqual({ decision: "approve" }) + }) + + it("should approve MCP tool when tool has alwaysAllow flag even if alwaysAllowMcp is false", async () => { + const mcpServerUse: McpServerUse = { + type: "use_mcp_tool", + serverName: "test-server", + toolName: "test-tool", + } + + const state: Partial = { + autoApprovalEnabled: true, + alwaysAllowMcp: false, // Global MCP auto-approval is disabled + mcpServers: [ + { + name: "test-server", + config: "test-config", + status: "connected", + tools: [ + { + name: "test-tool", + alwaysAllow: true, // Tool has individual alwaysAllow flag + }, + ], + }, + ] as any, + } + + const result = await checkAutoApproval({ + state: state as ExtensionState, + ask: "use_mcp_server", + text: JSON.stringify(mcpServerUse), + }) + + // Should approve because tool has alwaysAllow flag + expect(result).toEqual({ decision: "approve" }) + }) + + it("should ask for approval when neither alwaysAllowMcp nor tool alwaysAllow is set", async () => { + const mcpServerUse: McpServerUse = { + type: "use_mcp_tool", + serverName: "test-server", + toolName: "test-tool", + } + + const state: Partial = { + autoApprovalEnabled: true, + alwaysAllowMcp: false, + mcpServers: [ + { + name: "test-server", + config: "test-config", + status: "connected", + tools: [ + { + name: "test-tool", + alwaysAllow: false, + }, + ], + }, + ] as any, + } + + const result = await checkAutoApproval({ + state: state as ExtensionState, + ask: "use_mcp_server", + text: JSON.stringify(mcpServerUse), + }) + + // Should ask because neither condition is met + expect(result).toEqual({ decision: "ask" }) + }) + + it("should approve MCP resource access when alwaysAllowMcp is enabled", async () => { + const mcpServerUse: McpServerUse = { + type: "access_mcp_resource", + serverName: "test-server", + uri: "test://resource", + } + + const state: Partial = { + autoApprovalEnabled: true, + alwaysAllowMcp: true, + } + + const result = await checkAutoApproval({ + state: state as ExtensionState, + ask: "use_mcp_server", + text: JSON.stringify(mcpServerUse), + }) + + // Should approve resource access when alwaysAllowMcp is true + expect(result).toEqual({ decision: "approve" }) + }) + + it("should ask for MCP resource access when alwaysAllowMcp is disabled", async () => { + const mcpServerUse: McpServerUse = { + type: "access_mcp_resource", + serverName: "test-server", + uri: "test://resource", + } + + const state: Partial = { + autoApprovalEnabled: true, + alwaysAllowMcp: false, + } + + const result = await checkAutoApproval({ + state: state as ExtensionState, + ask: "use_mcp_server", + text: JSON.stringify(mcpServerUse), + }) + + // Should ask for resource access when alwaysAllowMcp is false + expect(result).toEqual({ decision: "ask" }) + }) + + it("should handle missing text gracefully", async () => { + const state: Partial = { + autoApprovalEnabled: true, + alwaysAllowMcp: true, + } + + const result = await checkAutoApproval({ + state: state as ExtensionState, + ask: "use_mcp_server", + text: undefined, + }) + + expect(result).toEqual({ decision: "ask" }) + }) + + it("should handle invalid JSON gracefully", async () => { + const state: Partial = { + autoApprovalEnabled: true, + alwaysAllowMcp: true, + } + + const result = await checkAutoApproval({ + state: state as ExtensionState, + ask: "use_mcp_server", + text: "invalid json", + }) + + expect(result).toEqual({ decision: "ask" }) + }) + }) + + describe("General auto-approval settings", () => { + it("should ask when autoApprovalEnabled is false", async () => { + const state: Partial = { + autoApprovalEnabled: false, + alwaysAllowMcp: true, + } + + const result = await checkAutoApproval({ + state: state as ExtensionState, + ask: "use_mcp_server", + text: JSON.stringify({ type: "use_mcp_tool", serverName: "test", toolName: "test" }), + }) + + expect(result).toEqual({ decision: "ask" }) + }) + + it("should ask when state is undefined", async () => { + const result = await checkAutoApproval({ + state: undefined, + ask: "use_mcp_server", + text: JSON.stringify({ type: "use_mcp_tool", serverName: "test", toolName: "test" }), + }) + + expect(result).toEqual({ decision: "ask" }) + }) + }) +}) diff --git a/src/core/auto-approval/index.ts b/src/core/auto-approval/index.ts index 52677932ce..96582635b6 100644 --- a/src/core/auto-approval/index.ts +++ b/src/core/auto-approval/index.ts @@ -99,7 +99,10 @@ export async function checkAutoApproval({ const mcpServerUse = JSON.parse(text) as McpServerUse if (mcpServerUse.type === "use_mcp_tool") { - return state.alwaysAllowMcp === true && isMcpToolAlwaysAllowed(mcpServerUse, state.mcpServers) + // Auto-approve if either: + // 1. alwaysAllowMcp is enabled (approves all MCP tools) + // 2. The specific tool has alwaysAllow flag set + return state.alwaysAllowMcp === true || isMcpToolAlwaysAllowed(mcpServerUse, state.mcpServers) ? { decision: "approve" } : { decision: "ask" } } else if (mcpServerUse.type === "access_mcp_resource") {