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", () => {