Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/fix-mcp-tools-chatgpt.md
Original file line number Diff line number Diff line change
@@ -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.
176 changes: 176 additions & 0 deletions src/core/prompts/sections/__tests__/mcp-servers.spec.ts
Original file line number Diff line number Diff line change
@@ -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")
})
})
1 change: 1 addition & 0 deletions src/core/prompts/sections/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
56 changes: 56 additions & 0 deletions src/core/prompts/sections/mcp-servers.ts
Original file line number Diff line number Diff line change
@@ -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")}`
}
6 changes: 5 additions & 1 deletion src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
addCustomInstructions,
markdownFormattingSection,
getSkillsSection,
getMcpServersSection,
} from "./sections"

// Helper function to get prompt component, filtering out empty objects
Expand Down Expand Up @@ -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()}
Expand All @@ -95,7 +99,7 @@ ${getSharedToolUseSection()}${toolsCatalog}
${getToolUseGuidelinesSection()}

${getCapabilitiesSection(cwd, shouldIncludeMcp ? mcpHub : undefined)}

${mcpServersSection ? `\n${mcpServersSection}\n` : ""}
${modesSection}
${skillsSection ? `\n${skillsSection}` : ""}
${getRulesSection(cwd, settings)}
Expand Down
Loading