mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
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
This commit is contained in:
parent
12cddc9697
commit
1a418f7452
4 changed files with 225 additions and 1 deletions
156
src/core/prompts/sections/__tests__/mcp-servers.spec.ts
Normal file
156
src/core/prompts/sections/__tests__/mcp-servers.spec.ts
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
import type { McpServer, McpTool } from "@roo-code/types"
|
||||
|
||||
import type { McpHub } from "../../../../services/mcp/McpHub"
|
||||
|
||||
import { getMcpServersSection } from "../mcp-servers"
|
||||
|
||||
describe("getMcpServersSection", () => {
|
||||
const createMockTool = (name: string, description = "Test tool", enabledForPrompt?: boolean): McpTool => ({
|
||||
name,
|
||||
description,
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
},
|
||||
...(enabledForPrompt !== undefined ? { enabledForPrompt } : {}),
|
||||
})
|
||||
|
||||
const createMockServer = (
|
||||
name: string,
|
||||
tools: McpTool[],
|
||||
options: { instructions?: string; source?: "global" | "project" } = {},
|
||||
): McpServer => ({
|
||||
name,
|
||||
config: JSON.stringify({ type: "stdio", command: "test" }),
|
||||
status: "connected",
|
||||
source: options.source ?? "global",
|
||||
tools,
|
||||
instructions: options.instructions,
|
||||
})
|
||||
|
||||
const createMockMcpHub = (servers: McpServer[]): Partial<McpHub> => ({
|
||||
getServers: vi.fn().mockReturnValue(servers),
|
||||
})
|
||||
|
||||
it("should return empty string when mcpHub is undefined", () => {
|
||||
const result = getMcpServersSection(undefined)
|
||||
expect(result).toBe("")
|
||||
})
|
||||
|
||||
it("should return empty string when no servers are available", () => {
|
||||
const mockHub = createMockMcpHub([])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).toBe("")
|
||||
})
|
||||
|
||||
it("should return empty string when servers have no enabled tools", () => {
|
||||
const server = createMockServer("testServer", [])
|
||||
const mockHub = createMockMcpHub([server])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).toBe("")
|
||||
})
|
||||
|
||||
it("should return empty string when all tools are disabled", () => {
|
||||
const server = createMockServer("testServer", [
|
||||
createMockTool("tool1", "Tool 1", false),
|
||||
createMockTool("tool2", "Tool 2", false),
|
||||
])
|
||||
const mockHub = createMockMcpHub([server])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).toBe("")
|
||||
})
|
||||
|
||||
it("should include MCP SERVERS header", () => {
|
||||
const server = createMockServer("testServer", [createMockTool("testTool")])
|
||||
const mockHub = createMockMcpHub([server])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).toContain("MCP SERVERS")
|
||||
})
|
||||
|
||||
it("should explain the naming convention", () => {
|
||||
const server = createMockServer("testServer", [createMockTool("testTool")])
|
||||
const mockHub = createMockMcpHub([server])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).toContain("mcp--serverName--toolName")
|
||||
})
|
||||
|
||||
it("should list server name as heading", () => {
|
||||
const server = createMockServer("context7", [createMockTool("resolve-library-id")])
|
||||
const mockHub = createMockMcpHub([server])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).toContain("## context7")
|
||||
})
|
||||
|
||||
it("should list tool names using the mcp-- naming convention", () => {
|
||||
const server = createMockServer("context7", [
|
||||
createMockTool("resolve-library-id"),
|
||||
createMockTool("get-library-docs"),
|
||||
])
|
||||
const mockHub = createMockMcpHub([server])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).toContain("mcp--context7--resolve-library-id")
|
||||
expect(result).toContain("mcp--context7--get-library-docs")
|
||||
})
|
||||
|
||||
it("should include server instructions when provided", () => {
|
||||
const server = createMockServer("conport", [createMockTool("init-db")], {
|
||||
instructions: "Always initialize the database before performing queries.",
|
||||
})
|
||||
const mockHub = createMockMcpHub([server])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).toContain("Server Instructions:")
|
||||
expect(result).toContain("Always initialize the database before performing queries.")
|
||||
})
|
||||
|
||||
it("should not include server instructions section when not provided", () => {
|
||||
const server = createMockServer("testServer", [createMockTool("testTool")])
|
||||
const mockHub = createMockMcpHub([server])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).not.toContain("Server Instructions:")
|
||||
})
|
||||
|
||||
it("should list multiple servers", () => {
|
||||
const server1 = createMockServer("context7", [createMockTool("resolve-library-id")])
|
||||
const server2 = createMockServer("git", [createMockTool("git-status")])
|
||||
const mockHub = createMockMcpHub([server1, server2])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).toContain("## context7")
|
||||
expect(result).toContain("## git")
|
||||
expect(result).toContain("mcp--context7--resolve-library-id")
|
||||
expect(result).toContain("mcp--git--git-status")
|
||||
})
|
||||
|
||||
it("should filter out disabled tools", () => {
|
||||
const server = createMockServer("testServer", [
|
||||
createMockTool("enabledTool", "Enabled tool"),
|
||||
createMockTool("disabledTool", "Disabled tool", false),
|
||||
])
|
||||
const mockHub = createMockMcpHub([server])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).toContain("mcp--testServer--enabledTool")
|
||||
expect(result).not.toContain("mcp--testServer--disabledTool")
|
||||
})
|
||||
|
||||
it("should skip servers with only disabled tools", () => {
|
||||
const serverWithDisabledTools = createMockServer("disabledServer", [createMockTool("tool1", "Tool 1", false)])
|
||||
const serverWithEnabledTools = createMockServer("enabledServer", [createMockTool("tool1", "Tool 1")])
|
||||
const mockHub = createMockMcpHub([serverWithDisabledTools, serverWithEnabledTools])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).not.toContain("## disabledServer")
|
||||
expect(result).toContain("## enabledServer")
|
||||
})
|
||||
|
||||
it("should skip servers with undefined tools", () => {
|
||||
const serverWithUndefinedTools: McpServer = {
|
||||
name: "noTools",
|
||||
config: JSON.stringify({ type: "stdio", command: "test" }),
|
||||
status: "connected",
|
||||
tools: undefined,
|
||||
}
|
||||
const serverWithTools = createMockServer("withTools", [createMockTool("tool1")])
|
||||
const mockHub = createMockMcpHub([serverWithUndefinedTools, serverWithTools])
|
||||
const result = getMcpServersSection(mockHub as McpHub)
|
||||
expect(result).not.toContain("## noTools")
|
||||
expect(result).toContain("## withTools")
|
||||
})
|
||||
})
|
||||
|
|
@ -8,3 +8,4 @@ export { getCapabilitiesSection } from "./capabilities"
|
|||
export { getModesSection } from "./modes"
|
||||
export { markdownFormattingSection } from "./markdown-formatting"
|
||||
export { getSkillsSection } from "./skills"
|
||||
export { getMcpServersSection } from "./mcp-servers"
|
||||
|
|
|
|||
64
src/core/prompts/sections/mcp-servers.ts
Normal file
64
src/core/prompts/sections/mcp-servers.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import type { McpTool } from "@roo-code/types"
|
||||
|
||||
import { McpHub } from "../../../services/mcp/McpHub"
|
||||
import { buildMcpToolName } from "../../../utils/mcp-name"
|
||||
|
||||
/**
|
||||
* Generates a lightweight MCP servers section for the system prompt.
|
||||
*
|
||||
* This provides the model with context about connected MCP servers, including:
|
||||
* - The naming convention for MCP tool calls (mcp--serverName--toolName)
|
||||
* - A list of connected servers and their available tools
|
||||
* - Server-specific instructions (from the MCP protocol's `instructions` field)
|
||||
*
|
||||
* This does NOT duplicate tool schemas or descriptions, since those are already
|
||||
* provided via native tool definitions. The purpose is to give the model enough
|
||||
* context to understand how to use these tools, which is especially important
|
||||
* for models like OpenAI's GPT series that benefit from explicit system prompt
|
||||
* guidance about available MCP tools.
|
||||
*/
|
||||
export function getMcpServersSection(mcpHub?: McpHub): string {
|
||||
if (!mcpHub) {
|
||||
return ""
|
||||
}
|
||||
|
||||
const servers = mcpHub.getServers()
|
||||
|
||||
if (servers.length === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Build per-server entries with tool names and optional instructions
|
||||
const serverEntries: string[] = []
|
||||
|
||||
for (const server of servers) {
|
||||
const enabledTools = (server.tools || []).filter((tool: McpTool) => tool.enabledForPrompt !== false)
|
||||
|
||||
if (enabledTools.length === 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
const toolNames = enabledTools.map((tool: McpTool) => ` - ${buildMcpToolName(server.name, tool.name)}`)
|
||||
|
||||
let entry = `## ${server.name}\n`
|
||||
entry += `Tools:\n${toolNames.join("\n")}`
|
||||
|
||||
if (server.instructions) {
|
||||
entry += `\n\nServer Instructions:\n${server.instructions}`
|
||||
}
|
||||
|
||||
serverEntries.push(entry)
|
||||
}
|
||||
|
||||
if (serverEntries.length === 0) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return `====
|
||||
|
||||
MCP SERVERS
|
||||
|
||||
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.
|
||||
|
||||
${serverEntries.join("\n\n")}`
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import {
|
|||
addCustomInstructions,
|
||||
markdownFormattingSection,
|
||||
getSkillsSection,
|
||||
getMcpServersSection,
|
||||
} from "./sections"
|
||||
|
||||
// Helper function to get prompt component, filtering out empty objects
|
||||
|
|
@ -86,6 +87,8 @@ async function generatePrompt(
|
|||
// Tools catalog is not included in the system prompt.
|
||||
const toolsCatalog = ""
|
||||
|
||||
const mcpServersSection = shouldIncludeMcp ? getMcpServersSection(mcpHub) : ""
|
||||
|
||||
const basePrompt = `${roleDefinition}
|
||||
|
||||
${markdownFormattingSection()}
|
||||
|
|
@ -95,7 +98,7 @@ ${getSharedToolUseSection()}${toolsCatalog}
|
|||
${getToolUseGuidelinesSection()}
|
||||
|
||||
${getCapabilitiesSection(cwd, shouldIncludeMcp ? mcpHub : undefined)}
|
||||
|
||||
${mcpServersSection ? `\n${mcpServersSection}` : ""}
|
||||
${modesSection}
|
||||
${skillsSection ? `\n${skillsSection}` : ""}
|
||||
${getRulesSection(cwd, settings)}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue