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

Commit 1a418f7

Browse files
committed
fix: re-add lightweight MCP servers section to system prompt for OpenAI compatibility
OpenAI models (e.g., GPT-5.2) fail to call MCP tools because they need explicit system prompt context about available MCP servers and the mcp--serverName--toolName naming convention. Claude and Gemini models handle this from native tool definitions alone, but OpenAI models do not. This commit adds a lightweight MCP SERVERS section to the system prompt that includes: - The mcp--serverName--toolName naming convention explanation - A list of connected servers and their tool names - Server-specific instructions (from the MCP protocol instructions field) This does NOT duplicate tool schemas or descriptions (already provided via native tool definitions), keeping the prompt compact. Closes #11317
1 parent 12cddc9 commit 1a418f7

4 files changed

Lines changed: 225 additions & 1 deletion

File tree

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
import type { McpServer, McpTool } from "@roo-code/types"
2+
3+
import type { McpHub } from "../../../../services/mcp/McpHub"
4+
5+
import { getMcpServersSection } from "../mcp-servers"
6+
7+
describe("getMcpServersSection", () => {
8+
const createMockTool = (name: string, description = "Test tool", enabledForPrompt?: boolean): McpTool => ({
9+
name,
10+
description,
11+
inputSchema: {
12+
type: "object",
13+
properties: {},
14+
},
15+
...(enabledForPrompt !== undefined ? { enabledForPrompt } : {}),
16+
})
17+
18+
const createMockServer = (
19+
name: string,
20+
tools: McpTool[],
21+
options: { instructions?: string; source?: "global" | "project" } = {},
22+
): McpServer => ({
23+
name,
24+
config: JSON.stringify({ type: "stdio", command: "test" }),
25+
status: "connected",
26+
source: options.source ?? "global",
27+
tools,
28+
instructions: options.instructions,
29+
})
30+
31+
const createMockMcpHub = (servers: McpServer[]): Partial<McpHub> => ({
32+
getServers: vi.fn().mockReturnValue(servers),
33+
})
34+
35+
it("should return empty string when mcpHub is undefined", () => {
36+
const result = getMcpServersSection(undefined)
37+
expect(result).toBe("")
38+
})
39+
40+
it("should return empty string when no servers are available", () => {
41+
const mockHub = createMockMcpHub([])
42+
const result = getMcpServersSection(mockHub as McpHub)
43+
expect(result).toBe("")
44+
})
45+
46+
it("should return empty string when servers have no enabled tools", () => {
47+
const server = createMockServer("testServer", [])
48+
const mockHub = createMockMcpHub([server])
49+
const result = getMcpServersSection(mockHub as McpHub)
50+
expect(result).toBe("")
51+
})
52+
53+
it("should return empty string when all tools are disabled", () => {
54+
const server = createMockServer("testServer", [
55+
createMockTool("tool1", "Tool 1", false),
56+
createMockTool("tool2", "Tool 2", false),
57+
])
58+
const mockHub = createMockMcpHub([server])
59+
const result = getMcpServersSection(mockHub as McpHub)
60+
expect(result).toBe("")
61+
})
62+
63+
it("should include MCP SERVERS header", () => {
64+
const server = createMockServer("testServer", [createMockTool("testTool")])
65+
const mockHub = createMockMcpHub([server])
66+
const result = getMcpServersSection(mockHub as McpHub)
67+
expect(result).toContain("MCP SERVERS")
68+
})
69+
70+
it("should explain the naming convention", () => {
71+
const server = createMockServer("testServer", [createMockTool("testTool")])
72+
const mockHub = createMockMcpHub([server])
73+
const result = getMcpServersSection(mockHub as McpHub)
74+
expect(result).toContain("mcp--serverName--toolName")
75+
})
76+
77+
it("should list server name as heading", () => {
78+
const server = createMockServer("context7", [createMockTool("resolve-library-id")])
79+
const mockHub = createMockMcpHub([server])
80+
const result = getMcpServersSection(mockHub as McpHub)
81+
expect(result).toContain("## context7")
82+
})
83+
84+
it("should list tool names using the mcp-- naming convention", () => {
85+
const server = createMockServer("context7", [
86+
createMockTool("resolve-library-id"),
87+
createMockTool("get-library-docs"),
88+
])
89+
const mockHub = createMockMcpHub([server])
90+
const result = getMcpServersSection(mockHub as McpHub)
91+
expect(result).toContain("mcp--context7--resolve-library-id")
92+
expect(result).toContain("mcp--context7--get-library-docs")
93+
})
94+
95+
it("should include server instructions when provided", () => {
96+
const server = createMockServer("conport", [createMockTool("init-db")], {
97+
instructions: "Always initialize the database before performing queries.",
98+
})
99+
const mockHub = createMockMcpHub([server])
100+
const result = getMcpServersSection(mockHub as McpHub)
101+
expect(result).toContain("Server Instructions:")
102+
expect(result).toContain("Always initialize the database before performing queries.")
103+
})
104+
105+
it("should not include server instructions section when not provided", () => {
106+
const server = createMockServer("testServer", [createMockTool("testTool")])
107+
const mockHub = createMockMcpHub([server])
108+
const result = getMcpServersSection(mockHub as McpHub)
109+
expect(result).not.toContain("Server Instructions:")
110+
})
111+
112+
it("should list multiple servers", () => {
113+
const server1 = createMockServer("context7", [createMockTool("resolve-library-id")])
114+
const server2 = createMockServer("git", [createMockTool("git-status")])
115+
const mockHub = createMockMcpHub([server1, server2])
116+
const result = getMcpServersSection(mockHub as McpHub)
117+
expect(result).toContain("## context7")
118+
expect(result).toContain("## git")
119+
expect(result).toContain("mcp--context7--resolve-library-id")
120+
expect(result).toContain("mcp--git--git-status")
121+
})
122+
123+
it("should filter out disabled tools", () => {
124+
const server = createMockServer("testServer", [
125+
createMockTool("enabledTool", "Enabled tool"),
126+
createMockTool("disabledTool", "Disabled tool", false),
127+
])
128+
const mockHub = createMockMcpHub([server])
129+
const result = getMcpServersSection(mockHub as McpHub)
130+
expect(result).toContain("mcp--testServer--enabledTool")
131+
expect(result).not.toContain("mcp--testServer--disabledTool")
132+
})
133+
134+
it("should skip servers with only disabled tools", () => {
135+
const serverWithDisabledTools = createMockServer("disabledServer", [createMockTool("tool1", "Tool 1", false)])
136+
const serverWithEnabledTools = createMockServer("enabledServer", [createMockTool("tool1", "Tool 1")])
137+
const mockHub = createMockMcpHub([serverWithDisabledTools, serverWithEnabledTools])
138+
const result = getMcpServersSection(mockHub as McpHub)
139+
expect(result).not.toContain("## disabledServer")
140+
expect(result).toContain("## enabledServer")
141+
})
142+
143+
it("should skip servers with undefined tools", () => {
144+
const serverWithUndefinedTools: McpServer = {
145+
name: "noTools",
146+
config: JSON.stringify({ type: "stdio", command: "test" }),
147+
status: "connected",
148+
tools: undefined,
149+
}
150+
const serverWithTools = createMockServer("withTools", [createMockTool("tool1")])
151+
const mockHub = createMockMcpHub([serverWithUndefinedTools, serverWithTools])
152+
const result = getMcpServersSection(mockHub as McpHub)
153+
expect(result).not.toContain("## noTools")
154+
expect(result).toContain("## withTools")
155+
})
156+
})

src/core/prompts/sections/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ export { getCapabilitiesSection } from "./capabilities"
88
export { getModesSection } from "./modes"
99
export { markdownFormattingSection } from "./markdown-formatting"
1010
export { getSkillsSection } from "./skills"
11+
export { getMcpServersSection } from "./mcp-servers"
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import type { McpTool } from "@roo-code/types"
2+
3+
import { McpHub } from "../../../services/mcp/McpHub"
4+
import { buildMcpToolName } from "../../../utils/mcp-name"
5+
6+
/**
7+
* Generates a lightweight MCP servers section for the system prompt.
8+
*
9+
* This provides the model with context about connected MCP servers, including:
10+
* - The naming convention for MCP tool calls (mcp--serverName--toolName)
11+
* - A list of connected servers and their available tools
12+
* - Server-specific instructions (from the MCP protocol's `instructions` field)
13+
*
14+
* This does NOT duplicate tool schemas or descriptions, since those are already
15+
* provided via native tool definitions. The purpose is to give the model enough
16+
* context to understand how to use these tools, which is especially important
17+
* for models like OpenAI's GPT series that benefit from explicit system prompt
18+
* guidance about available MCP tools.
19+
*/
20+
export function getMcpServersSection(mcpHub?: McpHub): string {
21+
if (!mcpHub) {
22+
return ""
23+
}
24+
25+
const servers = mcpHub.getServers()
26+
27+
if (servers.length === 0) {
28+
return ""
29+
}
30+
31+
// Build per-server entries with tool names and optional instructions
32+
const serverEntries: string[] = []
33+
34+
for (const server of servers) {
35+
const enabledTools = (server.tools || []).filter((tool: McpTool) => tool.enabledForPrompt !== false)
36+
37+
if (enabledTools.length === 0) {
38+
continue
39+
}
40+
41+
const toolNames = enabledTools.map((tool: McpTool) => ` - ${buildMcpToolName(server.name, tool.name)}`)
42+
43+
let entry = `## ${server.name}\n`
44+
entry += `Tools:\n${toolNames.join("\n")}`
45+
46+
if (server.instructions) {
47+
entry += `\n\nServer Instructions:\n${server.instructions}`
48+
}
49+
50+
serverEntries.push(entry)
51+
}
52+
53+
if (serverEntries.length === 0) {
54+
return ""
55+
}
56+
57+
return `====
58+
59+
MCP SERVERS
60+
61+
The following MCP (Model Context Protocol) servers are connected and provide additional tools you can use. MCP tools are called as native tool calls using the naming convention \`mcp--serverName--toolName\`. When a task could benefit from an MCP tool, prefer using it.
62+
63+
${serverEntries.join("\n\n")}`
64+
}

src/core/prompts/system.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import {
2626
addCustomInstructions,
2727
markdownFormattingSection,
2828
getSkillsSection,
29+
getMcpServersSection,
2930
} from "./sections"
3031

3132
// Helper function to get prompt component, filtering out empty objects
@@ -86,6 +87,8 @@ async function generatePrompt(
8687
// Tools catalog is not included in the system prompt.
8788
const toolsCatalog = ""
8889

90+
const mcpServersSection = shouldIncludeMcp ? getMcpServersSection(mcpHub) : ""
91+
8992
const basePrompt = `${roleDefinition}
9093
9194
${markdownFormattingSection()}
@@ -95,7 +98,7 @@ ${getSharedToolUseSection()}${toolsCatalog}
9598
${getToolUseGuidelinesSection()}
9699
97100
${getCapabilitiesSection(cwd, shouldIncludeMcp ? mcpHub : undefined)}
98-
101+
${mcpServersSection ? `\n${mcpServersSection}` : ""}
99102
${modesSection}
100103
${skillsSection ? `\n${skillsSection}` : ""}
101104
${getRulesSection(cwd, settings)}

0 commit comments

Comments
 (0)