Skip to content

Commit 793d594

Browse files
committed
fix: exclude access_mcp_resource tool when MCP has no resources
- Add mcpHub parameter to filterNativeToolsForMode() to check for available resources - Exclude access_mcp_resource when mcpHub is undefined or has no resources - Add helper function hasAnyMcpResources() to check resource availability - Update buildNativeToolsArray() to pass mcpHub to filter function - Add comprehensive tests for the new filtering behavior This ensures the native tool behavior matches the XML tool implementation, where access_mcp_resource returns undefined when !args.mcpHub.
1 parent 56c630c commit 793d594

File tree

3 files changed

+176
-8
lines changed

3 files changed

+176
-8
lines changed

src/core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts

Lines changed: 159 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,15 @@ describe("filterNativeToolsForMode", () => {
7171
groups: ["read", "browser", "mcp"] as const,
7272
}
7373

74-
const filtered = filterNativeToolsForMode(mockNativeTools, "architect", [architectMode], {}, undefined, {})
74+
const filtered = filterNativeToolsForMode(
75+
mockNativeTools,
76+
"architect",
77+
[architectMode],
78+
{},
79+
undefined,
80+
{},
81+
undefined,
82+
)
7583

7684
const toolNames = filtered.map((t) => ("function" in t ? t.function.name : ""))
7785

@@ -101,7 +109,7 @@ describe("filterNativeToolsForMode", () => {
101109
groups: ["read", "edit", "browser", "command", "mcp"] as const,
102110
}
103111

104-
const filtered = filterNativeToolsForMode(mockNativeTools, "code", [codeMode], {}, undefined, {})
112+
const filtered = filterNativeToolsForMode(mockNativeTools, "code", [codeMode], {}, undefined, {}, undefined)
105113

106114
const toolNames = filtered.map((t) => ("function" in t ? t.function.name : ""))
107115

@@ -123,7 +131,15 @@ describe("filterNativeToolsForMode", () => {
123131
groups: [] as const, // No groups
124132
}
125133

126-
const filtered = filterNativeToolsForMode(mockNativeTools, "restrictive", [restrictiveMode], {}, undefined, {})
134+
const filtered = filterNativeToolsForMode(
135+
mockNativeTools,
136+
"restrictive",
137+
[restrictiveMode],
138+
{},
139+
undefined,
140+
{},
141+
undefined,
142+
)
127143

128144
const toolNames = filtered.map((t) => ("function" in t ? t.function.name : ""))
129145

@@ -138,7 +154,7 @@ describe("filterNativeToolsForMode", () => {
138154
})
139155

140156
it("should handle undefined mode by using default mode", () => {
141-
const filtered = filterNativeToolsForMode(mockNativeTools, undefined, undefined, {}, undefined, {})
157+
const filtered = filterNativeToolsForMode(mockNativeTools, undefined, undefined, {}, undefined, {}, undefined)
142158

143159
// Should return some tools (default mode is code which has all groups)
144160
expect(filtered.length).toBeGreaterThan(0)
@@ -168,11 +184,136 @@ describe("filterNativeToolsForMode", () => {
168184
const toolsWithCodebaseSearch = [...mockNativeTools, mockCodebaseSearchTool]
169185

170186
// Without codeIndexManager
171-
const filtered = filterNativeToolsForMode(toolsWithCodebaseSearch, "code", [codeMode], {}, undefined, {})
187+
const filtered = filterNativeToolsForMode(
188+
toolsWithCodebaseSearch,
189+
"code",
190+
[codeMode],
191+
{},
192+
undefined,
193+
{},
194+
undefined,
195+
)
172196
const toolNames = filtered.map((t) => ("function" in t ? t.function.name : ""))
173197
expect(toolNames).not.toContain("codebase_search")
174198
})
175199

200+
it("should exclude access_mcp_resource when mcpHub is not provided", () => {
201+
const codeMode: ModeConfig = {
202+
slug: "code",
203+
name: "Code",
204+
roleDefinition: "Test",
205+
groups: ["read", "edit", "browser", "command", "mcp"] as const,
206+
}
207+
208+
const mockAccessMcpResourceTool: OpenAI.Chat.ChatCompletionTool = {
209+
type: "function",
210+
function: {
211+
name: "access_mcp_resource",
212+
description: "Access MCP resource",
213+
parameters: {},
214+
},
215+
}
216+
217+
const toolsWithAccessMcpResource = [...mockNativeTools, mockAccessMcpResourceTool]
218+
219+
// Without mcpHub
220+
const filtered = filterNativeToolsForMode(
221+
toolsWithAccessMcpResource,
222+
"code",
223+
[codeMode],
224+
{},
225+
undefined,
226+
{},
227+
undefined,
228+
)
229+
const toolNames = filtered.map((t) => ("function" in t ? t.function.name : ""))
230+
expect(toolNames).not.toContain("access_mcp_resource")
231+
})
232+
233+
it("should exclude access_mcp_resource when mcpHub has no resources", () => {
234+
const codeMode: ModeConfig = {
235+
slug: "code",
236+
name: "Code",
237+
roleDefinition: "Test",
238+
groups: ["read", "edit", "browser", "command", "mcp"] as const,
239+
}
240+
241+
const mockAccessMcpResourceTool: OpenAI.Chat.ChatCompletionTool = {
242+
type: "function",
243+
function: {
244+
name: "access_mcp_resource",
245+
description: "Access MCP resource",
246+
parameters: {},
247+
},
248+
}
249+
250+
const toolsWithAccessMcpResource = [...mockNativeTools, mockAccessMcpResourceTool]
251+
252+
// Mock mcpHub with no resources
253+
const mockMcpHub = {
254+
getServers: () => [
255+
{
256+
name: "test-server",
257+
resources: [],
258+
},
259+
],
260+
} as any
261+
262+
const filtered = filterNativeToolsForMode(
263+
toolsWithAccessMcpResource,
264+
"code",
265+
[codeMode],
266+
{},
267+
undefined,
268+
{},
269+
mockMcpHub,
270+
)
271+
const toolNames = filtered.map((t) => ("function" in t ? t.function.name : ""))
272+
expect(toolNames).not.toContain("access_mcp_resource")
273+
})
274+
275+
it("should include access_mcp_resource when mcpHub has resources", () => {
276+
const codeMode: ModeConfig = {
277+
slug: "code",
278+
name: "Code",
279+
roleDefinition: "Test",
280+
groups: ["read", "edit", "browser", "command", "mcp"] as const,
281+
}
282+
283+
const mockAccessMcpResourceTool: OpenAI.Chat.ChatCompletionTool = {
284+
type: "function",
285+
function: {
286+
name: "access_mcp_resource",
287+
description: "Access MCP resource",
288+
parameters: {},
289+
},
290+
}
291+
292+
const toolsWithAccessMcpResource = [...mockNativeTools, mockAccessMcpResourceTool]
293+
294+
// Mock mcpHub with resources
295+
const mockMcpHub = {
296+
getServers: () => [
297+
{
298+
name: "test-server",
299+
resources: [{ uri: "test://resource", name: "Test Resource" }],
300+
},
301+
],
302+
} as any
303+
304+
const filtered = filterNativeToolsForMode(
305+
toolsWithAccessMcpResource,
306+
"code",
307+
[codeMode],
308+
{},
309+
undefined,
310+
{},
311+
mockMcpHub,
312+
)
313+
const toolNames = filtered.map((t) => ("function" in t ? t.function.name : ""))
314+
expect(toolNames).toContain("access_mcp_resource")
315+
})
316+
176317
it("should exclude update_todo_list when todoListEnabled is false", () => {
177318
const codeMode: ModeConfig = {
178319
slug: "code",
@@ -192,9 +333,17 @@ describe("filterNativeToolsForMode", () => {
192333

193334
const toolsWithTodo = [...mockNativeTools, mockTodoTool]
194335

195-
const filtered = filterNativeToolsForMode(toolsWithTodo, "code", [codeMode], {}, undefined, {
196-
todoListEnabled: false,
197-
})
336+
const filtered = filterNativeToolsForMode(
337+
toolsWithTodo,
338+
"code",
339+
[codeMode],
340+
{},
341+
undefined,
342+
{
343+
todoListEnabled: false,
344+
},
345+
undefined,
346+
)
198347
const toolNames = filtered.map((t) => ("function" in t ? t.function.name : ""))
199348
expect(toolNames).not.toContain("update_todo_list")
200349
})
@@ -225,6 +374,7 @@ describe("filterNativeToolsForMode", () => {
225374
{ imageGeneration: false },
226375
undefined,
227376
{},
377+
undefined,
228378
)
229379
const toolNames = filtered.map((t) => ("function" in t ? t.function.name : ""))
230380
expect(toolNames).not.toContain("generate_image")
@@ -256,6 +406,7 @@ describe("filterNativeToolsForMode", () => {
256406
{ runSlashCommand: false },
257407
undefined,
258408
{},
409+
undefined,
259410
)
260411
const toolNames = filtered.map((t) => ("function" in t ? t.function.name : ""))
261412
expect(toolNames).not.toContain("run_slash_command")

src/core/prompts/tools/filter-tools-for-mode.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { getModeBySlug, getToolsForMode, isToolAllowedForMode } from "../../../s
44
import { TOOL_GROUPS, ALWAYS_AVAILABLE_TOOLS } from "../../../shared/tools"
55
import { defaultModeSlug } from "../../../shared/modes"
66
import type { CodeIndexManager } from "../../../services/code-index/manager"
7+
import type { McpHub } from "../../../services/mcp/McpHub"
78

89
/**
910
* Filters native tools based on mode restrictions.
@@ -15,6 +16,7 @@ import type { CodeIndexManager } from "../../../services/code-index/manager"
1516
* @param experiments - Experiment flags
1617
* @param codeIndexManager - Code index manager for codebase_search feature check
1718
* @param settings - Additional settings for tool filtering
19+
* @param mcpHub - MCP hub for checking available resources
1820
* @returns Filtered array of tools allowed for the mode
1921
*/
2022
export function filterNativeToolsForMode(
@@ -24,6 +26,7 @@ export function filterNativeToolsForMode(
2426
experiments: Record<string, boolean> | undefined,
2527
codeIndexManager?: CodeIndexManager,
2628
settings?: Record<string, any>,
29+
mcpHub?: McpHub,
2730
): OpenAI.Chat.ChatCompletionTool[] {
2831
// Get mode configuration and all tools for this mode
2932
const modeSlug = mode ?? defaultModeSlug
@@ -81,6 +84,11 @@ export function filterNativeToolsForMode(
8184
allowedToolNames.delete("browser_action")
8285
}
8386

87+
// Conditionally exclude access_mcp_resource if MCP is not enabled or there are no resources
88+
if (!mcpHub || !hasAnyMcpResources(mcpHub)) {
89+
allowedToolNames.delete("access_mcp_resource")
90+
}
91+
8492
// Filter native tools based on allowed tool names
8593
return nativeTools.filter((tool) => {
8694
// Handle both ChatCompletionTool and ChatCompletionCustomTool
@@ -91,6 +99,14 @@ export function filterNativeToolsForMode(
9199
})
92100
}
93101

102+
/**
103+
* Helper function to check if any MCP server has resources available
104+
*/
105+
function hasAnyMcpResources(mcpHub: McpHub): boolean {
106+
const servers = mcpHub.getServers()
107+
return servers.some((server) => server.resources && server.resources.length > 0)
108+
}
109+
94110
/**
95111
* Checks if a specific tool is allowed in the current mode.
96112
* This is useful for dynamically filtering system prompt content.

src/core/task/build-tools.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ export async function buildNativeToolsArray(options: BuildToolsOptions): Promise
5252
experiments,
5353
codeIndexManager,
5454
filterSettings,
55+
mcpHub,
5556
)
5657

5758
// Filter MCP tools based on mode restrictions

0 commit comments

Comments
 (0)