Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 12d7ea0

Browse files
committed
fix: include resource templates in access_mcp_resource tool filtering and description
- Fix hasAnyMcpResources() to also check resourceTemplates, not just static resources. MCP servers that only expose resource templates (URI templates with parameters) were incorrectly excluded. - Make access_mcp_resource tool description dynamic to include available resources and resource templates from connected MCP servers, giving the model the information it needs to construct valid resource URIs. - Pass mcpHub through getNativeTools() and build-tools.ts so the dynamic description is generated at tool assembly time. - Add comprehensive tests for both fixes.
1 parent ad25634 commit 12d7ea0

6 files changed

Lines changed: 399 additions & 34 deletions

File tree

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

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// npx vitest run core/prompts/tools/__tests__/filter-tools-for-mode.spec.ts
22

33
import type OpenAI from "openai"
4+
import type { McpHub } from "../../../../services/mcp/McpHub"
5+
import type { McpServer } from "@roo-code/types"
46

57
import { filterNativeToolsForMode } from "../filter-tools-for-mode"
68

@@ -15,6 +17,12 @@ function makeTool(name: string): OpenAI.Chat.ChatCompletionTool {
1517
} as OpenAI.Chat.ChatCompletionTool
1618
}
1719

20+
function createMockMcpHub(servers: McpServer[]): McpHub {
21+
return {
22+
getServers: () => servers,
23+
} as unknown as McpHub
24+
}
25+
1826
describe("filterNativeToolsForMode - disabledTools", () => {
1927
const nativeTools: OpenAI.Chat.ChatCompletionTool[] = [
2028
makeTool("execute_command"),
@@ -89,3 +97,109 @@ describe("filterNativeToolsForMode - disabledTools", () => {
8997
expect(resultNames).not.toContain("edit")
9098
})
9199
})
100+
101+
describe("filterNativeToolsForMode - access_mcp_resource with resource templates", () => {
102+
const toolsWithMcpResource: OpenAI.Chat.ChatCompletionTool[] = [
103+
makeTool("read_file"),
104+
makeTool("access_mcp_resource"),
105+
]
106+
107+
it("includes access_mcp_resource when server has only resource templates", () => {
108+
const mcpHub = createMockMcpHub([
109+
{
110+
name: "template-server",
111+
config: "{}",
112+
status: "connected",
113+
resources: [],
114+
resourceTemplates: [
115+
{
116+
uriTemplate: "test://resource/{id}",
117+
name: "Test Resource",
118+
},
119+
],
120+
},
121+
])
122+
123+
const result = filterNativeToolsForMode(
124+
toolsWithMcpResource,
125+
"code",
126+
undefined,
127+
undefined,
128+
undefined,
129+
undefined,
130+
mcpHub,
131+
)
132+
133+
const resultNames = result.map((t) => (t as any).function.name)
134+
expect(resultNames).toContain("access_mcp_resource")
135+
})
136+
137+
it("includes access_mcp_resource when server has only static resources", () => {
138+
const mcpHub = createMockMcpHub([
139+
{
140+
name: "static-server",
141+
config: "{}",
142+
status: "connected",
143+
resources: [
144+
{
145+
uri: "test://static",
146+
name: "Static Resource",
147+
},
148+
],
149+
resourceTemplates: [],
150+
},
151+
])
152+
153+
const result = filterNativeToolsForMode(
154+
toolsWithMcpResource,
155+
"code",
156+
undefined,
157+
undefined,
158+
undefined,
159+
undefined,
160+
mcpHub,
161+
)
162+
163+
const resultNames = result.map((t) => (t as any).function.name)
164+
expect(resultNames).toContain("access_mcp_resource")
165+
})
166+
167+
it("excludes access_mcp_resource when no mcpHub is provided", () => {
168+
const result = filterNativeToolsForMode(
169+
toolsWithMcpResource,
170+
"code",
171+
undefined,
172+
undefined,
173+
undefined,
174+
undefined,
175+
)
176+
177+
const resultNames = result.map((t) => (t as any).function.name)
178+
expect(resultNames).not.toContain("access_mcp_resource")
179+
})
180+
181+
it("excludes access_mcp_resource when server has no resources or templates", () => {
182+
const mcpHub = createMockMcpHub([
183+
{
184+
name: "empty-server",
185+
config: "{}",
186+
status: "connected",
187+
resources: [],
188+
resourceTemplates: [],
189+
},
190+
])
191+
192+
const result = filterNativeToolsForMode(
193+
toolsWithMcpResource,
194+
"code",
195+
undefined,
196+
undefined,
197+
undefined,
198+
undefined,
199+
mcpHub,
200+
)
201+
202+
const resultNames = result.map((t) => (t as any).function.name)
203+
expect(resultNames).not.toContain("access_mcp_resource")
204+
})
205+
})

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -330,11 +330,15 @@ export function filterNativeToolsForMode(
330330
}
331331

332332
/**
333-
* Helper function to check if any MCP server has resources available
333+
* Helper function to check if any MCP server has resources or resource templates available
334334
*/
335335
function hasAnyMcpResources(mcpHub: McpHub): boolean {
336336
const servers = mcpHub.getServers()
337-
return servers.some((server) => server.resources && server.resources.length > 0)
337+
return servers.some(
338+
(server) =>
339+
(server.resources && server.resources.length > 0) ||
340+
(server.resourceTemplates && server.resourceTemplates.length > 0),
341+
)
338342
}
339343

340344
/**
Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// npx vitest run core/prompts/tools/native-tools/__tests__/access_mcp_resource.spec.ts
2+
3+
import type OpenAI from "openai"
4+
5+
import type { McpServer } from "@roo-code/types"
6+
7+
import type { McpHub } from "../../../../../services/mcp/McpHub"
8+
import { createAccessMcpResourceTool } from "../access_mcp_resource"
9+
10+
// Helper type to access function tools
11+
type FunctionTool = OpenAI.Chat.ChatCompletionTool & { type: "function" }
12+
13+
// Helper to get the function property from a tool
14+
const getFunction = (tool: OpenAI.Chat.ChatCompletionTool) => (tool as FunctionTool).function
15+
16+
function createMockMcpHub(servers: McpServer[]): McpHub {
17+
return {
18+
getServers: () => servers,
19+
} as unknown as McpHub
20+
}
21+
22+
describe("createAccessMcpResourceTool", () => {
23+
it("returns base description when no mcpHub is provided", () => {
24+
const tool = createAccessMcpResourceTool()
25+
const desc = getFunction(tool).description!
26+
expect(desc).toContain("Request to access a resource provided by a connected MCP server")
27+
expect(desc).not.toContain("Available MCP Resources")
28+
})
29+
30+
it("returns base description when mcpHub has no servers", () => {
31+
const mcpHub = createMockMcpHub([])
32+
const tool = createAccessMcpResourceTool(mcpHub)
33+
const desc = getFunction(tool).description!
34+
expect(desc).not.toContain("Available MCP Resources")
35+
})
36+
37+
it("returns base description when servers have no resources or templates", () => {
38+
const mcpHub = createMockMcpHub([
39+
{
40+
name: "test-server",
41+
config: "{}",
42+
status: "connected",
43+
tools: [],
44+
resources: [],
45+
resourceTemplates: [],
46+
},
47+
])
48+
const tool = createAccessMcpResourceTool(mcpHub)
49+
const desc = getFunction(tool).description!
50+
expect(desc).not.toContain("Available MCP Resources")
51+
})
52+
53+
it("includes static resources in description", () => {
54+
const mcpHub = createMockMcpHub([
55+
{
56+
name: "weather-server",
57+
config: "{}",
58+
status: "connected",
59+
resources: [
60+
{
61+
uri: "weather://current",
62+
name: "Current Weather",
63+
description: "Get current weather data",
64+
},
65+
],
66+
resourceTemplates: [],
67+
},
68+
])
69+
const tool = createAccessMcpResourceTool(mcpHub)
70+
const desc = getFunction(tool).description!
71+
expect(desc).toContain("Available MCP Resources")
72+
expect(desc).toContain('Server "weather-server" resources:')
73+
expect(desc).toContain("weather://current")
74+
expect(desc).toContain("Current Weather")
75+
expect(desc).toContain("Get current weather data")
76+
})
77+
78+
it("includes resource templates in description", () => {
79+
const mcpHub = createMockMcpHub([
80+
{
81+
name: "orbitcity",
82+
config: "{}",
83+
status: "connected",
84+
resources: [],
85+
resourceTemplates: [
86+
{
87+
uriTemplate: "orbitcity://event/details/{identifier}",
88+
name: "Event Details",
89+
description: "Get event details by identifier",
90+
},
91+
{
92+
uriTemplate: "orbitcity://system/history/{machine_type}/{serial}",
93+
name: "System History",
94+
description: "Get system history",
95+
},
96+
],
97+
},
98+
])
99+
const tool = createAccessMcpResourceTool(mcpHub)
100+
const desc = getFunction(tool).description!
101+
expect(desc).toContain("Available MCP Resources")
102+
expect(desc).toContain('Server "orbitcity" resources:')
103+
expect(desc).toContain("orbitcity://event/details/{identifier}")
104+
expect(desc).toContain("Event Details")
105+
expect(desc).toContain("orbitcity://system/history/{machine_type}/{serial}")
106+
expect(desc).toContain("System History")
107+
})
108+
109+
it("includes both resources and resource templates from the same server", () => {
110+
const mcpHub = createMockMcpHub([
111+
{
112+
name: "data-server",
113+
config: "{}",
114+
status: "connected",
115+
resources: [
116+
{
117+
uri: "data://status",
118+
name: "Server Status",
119+
},
120+
],
121+
resourceTemplates: [
122+
{
123+
uriTemplate: "data://records/{id}",
124+
name: "Record by ID",
125+
},
126+
],
127+
},
128+
])
129+
const tool = createAccessMcpResourceTool(mcpHub)
130+
const desc = getFunction(tool).description!
131+
expect(desc).toContain("data://status")
132+
expect(desc).toContain("data://records/{id}")
133+
})
134+
135+
it("includes resources from multiple servers", () => {
136+
const mcpHub = createMockMcpHub([
137+
{
138+
name: "server-a",
139+
config: "{}",
140+
status: "connected",
141+
resources: [{ uri: "a://resource", name: "Resource A" }],
142+
},
143+
{
144+
name: "server-b",
145+
config: "{}",
146+
status: "connected",
147+
resourceTemplates: [{ uriTemplate: "b://template/{id}", name: "Template B" }],
148+
},
149+
])
150+
const tool = createAccessMcpResourceTool(mcpHub)
151+
const desc = getFunction(tool).description!
152+
expect(desc).toContain('Server "server-a" resources:')
153+
expect(desc).toContain("a://resource")
154+
expect(desc).toContain('Server "server-b" resources:')
155+
expect(desc).toContain("b://template/{id}")
156+
})
157+
158+
it("handles resources without descriptions", () => {
159+
const mcpHub = createMockMcpHub([
160+
{
161+
name: "minimal-server",
162+
config: "{}",
163+
status: "connected",
164+
resources: [{ uri: "min://data", name: "Data" }],
165+
},
166+
])
167+
const tool = createAccessMcpResourceTool(mcpHub)
168+
const desc = getFunction(tool).description!
169+
expect(desc).toContain("min://data (Data)")
170+
})
171+
172+
it("always has correct tool name and parameters", () => {
173+
const tool = createAccessMcpResourceTool()
174+
const fn = getFunction(tool)
175+
expect(fn.name).toBe("access_mcp_resource")
176+
expect(fn.parameters).toBeDefined()
177+
const params = fn.parameters as Record<string, unknown>
178+
expect(params.required).toContain("server_name")
179+
expect(params.required).toContain("uri")
180+
})
181+
})

0 commit comments

Comments
 (0)