Skip to content

Commit c28478e

Browse files
feat: add wildcard support for MCP alwaysAllow configuration (#10948)
Co-authored-by: Roo Code <roomote@roocode.com>
1 parent c56f0f4 commit c28478e

File tree

2 files changed

+144
-1
lines changed

2 files changed

+144
-1
lines changed

src/services/mcp/McpHub.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1009,10 +1009,13 @@ export class McpHub {
10091009
// Continue with empty configs
10101010
}
10111011

1012+
// Check if wildcard "*" is in the alwaysAllow config
1013+
const hasWildcard = alwaysAllowConfig.includes("*")
1014+
10121015
// Mark tools as always allowed and enabled for prompt based on settings
10131016
const tools = (response?.tools || []).map((tool) => ({
10141017
...tool,
1015-
alwaysAllow: alwaysAllowConfig.includes(tool.name),
1018+
alwaysAllow: hasWildcard || alwaysAllowConfig.includes(tool.name),
10161019
enabledForPrompt: !disabledToolsList.includes(tool.name),
10171020
}))
10181021

src/services/mcp/__tests__/McpHub.spec.ts

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -911,6 +911,146 @@ describe("McpHub", () => {
911911
expect(writtenConfig.mcpServers["test-server"].alwaysAllow).toBeDefined()
912912
expect(writtenConfig.mcpServers["test-server"].alwaysAllow).toContain("new-tool")
913913
})
914+
915+
it("should mark all tools as always allowed when wildcard is present", async () => {
916+
const mockConfig = {
917+
mcpServers: {
918+
"test-server": {
919+
type: "stdio",
920+
command: "node",
921+
args: ["test.js"],
922+
alwaysAllow: ["*"],
923+
},
924+
},
925+
}
926+
927+
// Mock reading config - needs to return for every read
928+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig))
929+
930+
// Set up mock connection with tools
931+
const mockConnection: ConnectedMcpConnection = {
932+
type: "connected",
933+
server: {
934+
name: "test-server",
935+
type: "stdio",
936+
command: "node",
937+
args: ["test.js"],
938+
source: "global",
939+
} as any,
940+
client: {
941+
request: vi.fn().mockResolvedValue({
942+
tools: [
943+
{ name: "tool1", description: "Tool 1" },
944+
{ name: "tool2", description: "Tool 2" },
945+
{ name: "tool3", description: "Tool 3" },
946+
],
947+
}),
948+
} as any,
949+
transport: {} as any,
950+
}
951+
mcpHub.connections = [mockConnection]
952+
953+
// Fetch tools list to test wildcard matching
954+
const tools = await mcpHub["fetchToolsList"]("test-server", "global")
955+
956+
// All tools should be marked as always allowed
957+
expect(tools.length).toBe(3)
958+
expect(tools[0].alwaysAllow).toBe(true)
959+
expect(tools[1].alwaysAllow).toBe(true)
960+
expect(tools[2].alwaysAllow).toBe(true)
961+
})
962+
963+
it("should support both wildcard and specific tool names in alwaysAllow", async () => {
964+
const mockConfig = {
965+
mcpServers: {
966+
"test-server": {
967+
type: "stdio",
968+
command: "node",
969+
args: ["test.js"],
970+
alwaysAllow: ["*", "specific-tool"],
971+
},
972+
},
973+
}
974+
975+
// Mock reading config - needs to return for every read
976+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig))
977+
978+
// Set up mock connection with tools
979+
const mockConnection: ConnectedMcpConnection = {
980+
type: "connected",
981+
server: {
982+
name: "test-server",
983+
type: "stdio",
984+
command: "node",
985+
args: ["test.js"],
986+
source: "global",
987+
} as any,
988+
client: {
989+
request: vi.fn().mockResolvedValue({
990+
tools: [
991+
{ name: "tool1", description: "Tool 1" },
992+
{ name: "specific-tool", description: "Specific Tool" },
993+
],
994+
}),
995+
} as any,
996+
transport: {} as any,
997+
}
998+
mcpHub.connections = [mockConnection]
999+
1000+
// Fetch tools list
1001+
const tools = await mcpHub["fetchToolsList"]("test-server", "global")
1002+
1003+
// All tools should be marked as always allowed due to wildcard
1004+
expect(tools.length).toBe(2)
1005+
expect(tools[0].alwaysAllow).toBe(true)
1006+
expect(tools[1].alwaysAllow).toBe(true)
1007+
})
1008+
1009+
it("should only allow specific tools when no wildcard is present", async () => {
1010+
const mockConfig = {
1011+
mcpServers: {
1012+
"test-server": {
1013+
type: "stdio",
1014+
command: "node",
1015+
args: ["test.js"],
1016+
alwaysAllow: ["allowed-tool"],
1017+
},
1018+
},
1019+
}
1020+
1021+
// Mock reading config - needs to return for every read
1022+
vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(mockConfig))
1023+
1024+
// Set up mock connection with tools
1025+
const mockConnection: ConnectedMcpConnection = {
1026+
type: "connected",
1027+
server: {
1028+
name: "test-server",
1029+
type: "stdio",
1030+
command: "node",
1031+
args: ["test.js"],
1032+
source: "global",
1033+
} as any,
1034+
client: {
1035+
request: vi.fn().mockResolvedValue({
1036+
tools: [
1037+
{ name: "allowed-tool", description: "Allowed Tool" },
1038+
{ name: "not-allowed-tool", description: "Not Allowed Tool" },
1039+
],
1040+
}),
1041+
} as any,
1042+
transport: {} as any,
1043+
}
1044+
mcpHub.connections = [mockConnection]
1045+
1046+
// Fetch tools list
1047+
const tools = await mcpHub["fetchToolsList"]("test-server", "global")
1048+
1049+
// Only the specifically allowed tool should be marked as always allowed
1050+
expect(tools.length).toBe(2)
1051+
expect(tools[0].alwaysAllow).toBe(true) // allowed-tool
1052+
expect(tools[1].alwaysAllow).toBe(false) // not-allowed-tool
1053+
})
9141054
})
9151055

9161056
describe("toggleToolEnabledForPrompt", () => {

0 commit comments

Comments
 (0)