From 803a2644513fa18cdc348d45ba4a6b1373569841 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Mon, 9 Feb 2026 01:49:35 +0000 Subject: [PATCH] fix: re-add lightweight MCP servers section to system prompt for OpenAI compatibility --- .changeset/fix-mcp-tools-chatgpt.md | 7 + .../sections/__tests__/mcp-servers.spec.ts | 176 ++++++++++++++++++ src/core/prompts/sections/index.ts | 1 + src/core/prompts/sections/mcp-servers.ts | 56 ++++++ src/core/prompts/system.ts | 6 +- 5 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 .changeset/fix-mcp-tools-chatgpt.md create mode 100644 src/core/prompts/sections/__tests__/mcp-servers.spec.ts create mode 100644 src/core/prompts/sections/mcp-servers.ts diff --git a/.changeset/fix-mcp-tools-chatgpt.md b/.changeset/fix-mcp-tools-chatgpt.md new file mode 100644 index 00000000000..3ec0fa9d9db --- /dev/null +++ b/.changeset/fix-mcp-tools-chatgpt.md @@ -0,0 +1,7 @@ +--- +"roo-cline": patch +--- + +Re-add lightweight MCP servers section to system prompt for better OpenAI/ChatGPT compatibility + +When MCP tools were migrated to native tool definitions (PR #10895), the MCP SERVERS section was removed from the system prompt. While Claude and Gemini models can infer MCP tool usage from native tool definitions alone, OpenAI models need additional context to understand the `mcp--serverName--toolName` naming convention. This adds back a lightweight section that lists connected MCP servers, their tool name mappings, server-specific instructions, and explains the naming convention -- without duplicating tool schemas that are already in the native tool definitions. diff --git a/src/core/prompts/sections/__tests__/mcp-servers.spec.ts b/src/core/prompts/sections/__tests__/mcp-servers.spec.ts new file mode 100644 index 00000000000..ff0cfec5068 --- /dev/null +++ b/src/core/prompts/sections/__tests__/mcp-servers.spec.ts @@ -0,0 +1,176 @@ +import type { McpHub } from "../../../../services/mcp/McpHub" + +import { getMcpServersSection } from "../mcp-servers" + +const createMockMcpHub = (servers: any[]): McpHub => + ({ + getServers: () => servers, + }) as unknown as McpHub + +describe("getMcpServersSection", () => { + 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 connected", () => { + const hub = createMockMcpHub([]) + const result = getMcpServersSection(hub) + expect(result).toBe("") + }) + + it("should return empty string when servers exist but none are connected", () => { + const hub = createMockMcpHub([ + { + name: "test-server", + status: "disconnected", + tools: [{ name: "tool1", description: "A tool" }], + }, + ]) + const result = getMcpServersSection(hub) + expect(result).toBe("") + }) + + it("should include connected server with tools", () => { + const hub = createMockMcpHub([ + { + name: "git", + status: "connected", + tools: [ + { name: "git_log", description: "Show git log" }, + { name: "git_status", description: "Show git status" }, + ], + }, + ]) + const result = getMcpServersSection(hub) + + expect(result).toContain("MCP SERVERS") + expect(result).toContain("## git") + expect(result).toContain("mcp--git--git_log") + expect(result).toContain("mcp--git--git_status") + expect(result).toContain("mcp--serverName--toolName") + }) + + it("should filter out tools with enabledForPrompt === false", () => { + const hub = createMockMcpHub([ + { + name: "testServer", + status: "connected", + tools: [ + { name: "enabled_tool", description: "Enabled", enabledForPrompt: true }, + { name: "disabled_tool", description: "Disabled", enabledForPrompt: false }, + { name: "default_tool", description: "Default (no flag)" }, + ], + }, + ]) + const result = getMcpServersSection(hub) + + expect(result).toContain("mcp--testServer--enabled_tool") + expect(result).not.toContain("disabled_tool") + expect(result).toContain("mcp--testServer--default_tool") + }) + + it("should include server instructions when available", () => { + const hub = createMockMcpHub([ + { + name: "context7", + status: "connected", + instructions: "Always use resolve-library-id before get-library-docs.", + tools: [{ name: "resolve-library-id", description: "Resolve lib" }], + }, + ]) + const result = getMcpServersSection(hub) + + expect(result).toContain("## context7") + expect(result).toContain("Instructions: Always use resolve-library-id before get-library-docs.") + expect(result).toContain("mcp--context7--resolve-library-id") + }) + + it("should not include instructions block when server has no instructions", () => { + const hub = createMockMcpHub([ + { + name: "simple-server", + status: "connected", + tools: [{ name: "do_thing", description: "Does a thing" }], + }, + ]) + const result = getMcpServersSection(hub) + + expect(result).toContain("## simple-server") + expect(result).not.toContain("Instructions:") + }) + + it("should handle server with no tools", () => { + const hub = createMockMcpHub([ + { + name: "no-tools-server", + status: "connected", + tools: undefined, + }, + ]) + const result = getMcpServersSection(hub) + + expect(result).toContain("## no-tools-server") + expect(result).toContain("(No tools available)") + }) + + it("should handle multiple connected servers", () => { + const hub = createMockMcpHub([ + { + name: "git", + status: "connected", + tools: [{ name: "git_log", description: "Show log" }], + }, + { + name: "context7", + status: "connected", + instructions: "Resolve library IDs first.", + tools: [{ name: "resolve-library-id", description: "Resolve lib" }], + }, + { + name: "offline-server", + status: "disconnected", + tools: [{ name: "should_not_appear", description: "Hidden" }], + }, + ]) + const result = getMcpServersSection(hub) + + expect(result).toContain("## git") + expect(result).toContain("## context7") + expect(result).not.toContain("## offline-server") + expect(result).not.toContain("should_not_appear") + }) + + it("should only include connected servers in the section", () => { + const hub = createMockMcpHub([ + { + name: "connecting-server", + status: "connecting", + tools: [{ name: "tool1", description: "Tool 1" }], + }, + ]) + const result = getMcpServersSection(hub) + + // "connecting" is not "connected", so no servers section should be generated + expect(result).toBe("") + }) + + it("should handle server with all tools disabled", () => { + const hub = createMockMcpHub([ + { + name: "all-disabled", + status: "connected", + tools: [ + { name: "tool1", description: "Tool 1", enabledForPrompt: false }, + { name: "tool2", description: "Tool 2", enabledForPrompt: false }, + ], + }, + ]) + const result = getMcpServersSection(hub) + + expect(result).toContain("## all-disabled") + expect(result).toContain("(No tools available)") + expect(result).not.toContain("mcp--all-disabled--tool1") + expect(result).not.toContain("mcp--all-disabled--tool2") + }) +}) diff --git a/src/core/prompts/sections/index.ts b/src/core/prompts/sections/index.ts index 318cd47bc9d..d5400cd1ae2 100644 --- a/src/core/prompts/sections/index.ts +++ b/src/core/prompts/sections/index.ts @@ -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" diff --git a/src/core/prompts/sections/mcp-servers.ts b/src/core/prompts/sections/mcp-servers.ts new file mode 100644 index 00000000000..fa18655466a --- /dev/null +++ b/src/core/prompts/sections/mcp-servers.ts @@ -0,0 +1,56 @@ +import { McpHub } from "../../../services/mcp/McpHub" +import { buildMcpToolName } from "../../../utils/mcp-name" + +/** + * Generates a lightweight MCP servers section for the system prompt. + * + * This section provides the model with context about connected MCP servers, + * their tool name mappings, and any server-specific instructions. It does NOT + * duplicate tool schemas (those are already provided via native tool definitions). + * + * This context is particularly important for models like OpenAI's GPT series, + * which need explicit guidance to understand the mcp--serverName--toolName + * naming convention used by native tool definitions. + */ +export function getMcpServersSection(mcpHub?: McpHub): string { + if (!mcpHub) { + return "" + } + + const servers = mcpHub.getServers() + const connectedServers = servers.filter((server) => server.status === "connected") + + if (connectedServers.length === 0) { + return "" + } + + const serverSections: string[] = [] + + for (const server of connectedServers) { + const toolLines: string[] = [] + + if (server.tools) { + for (const tool of server.tools) { + if (tool.enabledForPrompt === false) { + continue + } + toolLines.push(` - ${buildMcpToolName(server.name, tool.name)}`) + } + } + + const toolList = toolLines.length > 0 ? toolLines.join("\n") : " (No tools available)" + const instructionsBlock = server.instructions ? `\nInstructions: ${server.instructions}` : "" + + serverSections.push(`## ${server.name}${instructionsBlock}\nTools:\n${toolList}`) + } + + return `==== + +MCP SERVERS + +MCP servers provide additional tools beyond the built-in set. Tools from MCP servers are called using the naming convention \`mcp--serverName--toolName\` (e.g., \`mcp--git--git_log\`). These tools are available as callable functions alongside the built-in tools. When a task could benefit from an MCP server's capabilities, prefer using the appropriate MCP tool. + +# Connected MCP Servers + +${serverSections.join("\n\n")}` +} diff --git a/src/core/prompts/system.ts b/src/core/prompts/system.ts index 1731952a4eb..67cff78665b 100644 --- a/src/core/prompts/system.ts +++ b/src/core/prompts/system.ts @@ -26,6 +26,7 @@ import { addCustomInstructions, markdownFormattingSection, getSkillsSection, + getMcpServersSection, } from "./sections" // Helper function to get prompt component, filtering out empty objects @@ -86,6 +87,9 @@ async function generatePrompt( // Tools catalog is not included in the system prompt. const toolsCatalog = "" + // Generate the lightweight MCP servers section (server names, tool mappings, instructions) + const mcpServersSection = shouldIncludeMcp ? getMcpServersSection(mcpHub) : "" + const basePrompt = `${roleDefinition} ${markdownFormattingSection()} @@ -95,7 +99,7 @@ ${getSharedToolUseSection()}${toolsCatalog} ${getToolUseGuidelinesSection()} ${getCapabilitiesSection(cwd, shouldIncludeMcp ? mcpHub : undefined)} - +${mcpServersSection ? `\n${mcpServersSection}\n` : ""} ${modesSection} ${skillsSection ? `\n${skillsSection}` : ""} ${getRulesSection(cwd, settings)}