From 297dcc0116c6ffc5a3538d703022ea4d0172f037 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sun, 25 Jan 2026 03:29:16 +0000 Subject: [PATCH 1/2] feat: add wildcard support for MCP alwaysAllow configuration - Add "*" wildcard support in alwaysAllow config to auto-approve all tools from an MCP server - Update fetchToolsList in McpHub to check for wildcard in alwaysAllow array - Add comprehensive tests for wildcard functionality - Ensures backward compatibility with existing specific tool configurations --- src/core/auto-approval/mcp.ts | 13 ++ src/services/mcp/McpHub.ts | 5 +- src/services/mcp/__tests__/McpHub.spec.ts | 140 ++++++++++++++++++++++ 3 files changed, 157 insertions(+), 1 deletion(-) diff --git a/src/core/auto-approval/mcp.ts b/src/core/auto-approval/mcp.ts index 4e576f4e380..0155a81eac5 100644 --- a/src/core/auto-approval/mcp.ts +++ b/src/core/auto-approval/mcp.ts @@ -1,5 +1,18 @@ import type { McpServerUse, McpServer, McpTool } from "@roo-code/types" +/** + * Checks if a tool name matches a pattern (supports wildcard "*") + * @param toolName The name of the tool to check + * @param pattern The pattern to match against (can be "*" for wildcard) + * @returns True if the tool name matches the pattern + */ +function matchesPattern(toolName: string, pattern: string): boolean { + if (pattern === "*") { + return true + } + return toolName === pattern +} + export function isMcpToolAlwaysAllowed(mcpServerUse: McpServerUse, mcpServers: McpServer[] | undefined): boolean { if (mcpServerUse.type === "use_mcp_tool" && mcpServerUse.toolName) { const server = mcpServers?.find((s: McpServer) => s.name === mcpServerUse.serverName) diff --git a/src/services/mcp/McpHub.ts b/src/services/mcp/McpHub.ts index f4ff73c62d7..3d02ce25f19 100644 --- a/src/services/mcp/McpHub.ts +++ b/src/services/mcp/McpHub.ts @@ -1009,10 +1009,13 @@ export class McpHub { // Continue with empty configs } + // Check if wildcard "*" is in the alwaysAllow config + const hasWildcard = alwaysAllowConfig.includes("*") + // Mark tools as always allowed and enabled for prompt based on settings const tools = (response?.tools || []).map((tool) => ({ ...tool, - alwaysAllow: alwaysAllowConfig.includes(tool.name), + alwaysAllow: hasWildcard || alwaysAllowConfig.includes(tool.name), enabledForPrompt: !disabledToolsList.includes(tool.name), })) diff --git a/src/services/mcp/__tests__/McpHub.spec.ts b/src/services/mcp/__tests__/McpHub.spec.ts index 2d895fdbca5..3f06627cc17 100644 --- a/src/services/mcp/__tests__/McpHub.spec.ts +++ b/src/services/mcp/__tests__/McpHub.spec.ts @@ -911,6 +911,146 @@ describe("McpHub", () => { expect(writtenConfig.mcpServers["test-server"].alwaysAllow).toBeDefined() expect(writtenConfig.mcpServers["test-server"].alwaysAllow).toContain("new-tool") }) + + it("should mark all tools as always allowed when wildcard is present", async () => { + const mockConfig = { + mcpServers: { + "test-server": { + type: "stdio", + command: "node", + args: ["test.js"], + alwaysAllow: ["*"], + }, + }, + } + + // Mock reading config - needs to return for every read + vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig)) + + // Set up mock connection with tools + const mockConnection: ConnectedMcpConnection = { + type: "connected", + server: { + name: "test-server", + type: "stdio", + command: "node", + args: ["test.js"], + source: "global", + } as any, + client: { + request: vi.fn().mockResolvedValue({ + tools: [ + { name: "tool1", description: "Tool 1" }, + { name: "tool2", description: "Tool 2" }, + { name: "tool3", description: "Tool 3" }, + ], + }), + } as any, + transport: {} as any, + } + mcpHub.connections = [mockConnection] + + // Fetch tools list to test wildcard matching + const tools = await mcpHub["fetchToolsList"]("test-server", "global") + + // All tools should be marked as always allowed + expect(tools.length).toBe(3) + expect(tools[0].alwaysAllow).toBe(true) + expect(tools[1].alwaysAllow).toBe(true) + expect(tools[2].alwaysAllow).toBe(true) + }) + + it("should support both wildcard and specific tool names in alwaysAllow", async () => { + const mockConfig = { + mcpServers: { + "test-server": { + type: "stdio", + command: "node", + args: ["test.js"], + alwaysAllow: ["*", "specific-tool"], + }, + }, + } + + // Mock reading config - needs to return for every read + vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig)) + + // Set up mock connection with tools + const mockConnection: ConnectedMcpConnection = { + type: "connected", + server: { + name: "test-server", + type: "stdio", + command: "node", + args: ["test.js"], + source: "global", + } as any, + client: { + request: vi.fn().mockResolvedValue({ + tools: [ + { name: "tool1", description: "Tool 1" }, + { name: "specific-tool", description: "Specific Tool" }, + ], + }), + } as any, + transport: {} as any, + } + mcpHub.connections = [mockConnection] + + // Fetch tools list + const tools = await mcpHub["fetchToolsList"]("test-server", "global") + + // All tools should be marked as always allowed due to wildcard + expect(tools.length).toBe(2) + expect(tools[0].alwaysAllow).toBe(true) + expect(tools[1].alwaysAllow).toBe(true) + }) + + it("should only allow specific tools when no wildcard is present", async () => { + const mockConfig = { + mcpServers: { + "test-server": { + type: "stdio", + command: "node", + args: ["test.js"], + alwaysAllow: ["allowed-tool"], + }, + }, + } + + // Mock reading config - needs to return for every read + vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig)) + + // Set up mock connection with tools + const mockConnection: ConnectedMcpConnection = { + type: "connected", + server: { + name: "test-server", + type: "stdio", + command: "node", + args: ["test.js"], + source: "global", + } as any, + client: { + request: vi.fn().mockResolvedValue({ + tools: [ + { name: "allowed-tool", description: "Allowed Tool" }, + { name: "not-allowed-tool", description: "Not Allowed Tool" }, + ], + }), + } as any, + transport: {} as any, + } + mcpHub.connections = [mockConnection] + + // Fetch tools list + const tools = await mcpHub["fetchToolsList"]("test-server", "global") + + // Only the specifically allowed tool should be marked as always allowed + expect(tools.length).toBe(2) + expect(tools[0].alwaysAllow).toBe(true) // allowed-tool + expect(tools[1].alwaysAllow).toBe(false) // not-allowed-tool + }) }) describe("toggleToolEnabledForPrompt", () => { From ebf0660bb41536bf6577c21da2ce266963e6a9fe Mon Sep 17 00:00:00 2001 From: Roo Code Date: Sun, 25 Jan 2026 03:32:41 +0000 Subject: [PATCH 2/2] refactor: remove unused matchesPattern helper function --- src/core/auto-approval/mcp.ts | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/core/auto-approval/mcp.ts b/src/core/auto-approval/mcp.ts index 0155a81eac5..4e576f4e380 100644 --- a/src/core/auto-approval/mcp.ts +++ b/src/core/auto-approval/mcp.ts @@ -1,18 +1,5 @@ import type { McpServerUse, McpServer, McpTool } from "@roo-code/types" -/** - * Checks if a tool name matches a pattern (supports wildcard "*") - * @param toolName The name of the tool to check - * @param pattern The pattern to match against (can be "*" for wildcard) - * @returns True if the tool name matches the pattern - */ -function matchesPattern(toolName: string, pattern: string): boolean { - if (pattern === "*") { - return true - } - return toolName === pattern -} - export function isMcpToolAlwaysAllowed(mcpServerUse: McpServerUse, mcpServers: McpServer[] | undefined): boolean { if (mcpServerUse.type === "use_mcp_tool" && mcpServerUse.toolName) { const server = mcpServers?.find((s: McpServer) => s.name === mcpServerUse.serverName)