Fixes #4900: MCP Tool reminders enforce the wrong syntax

- Modified formatResponse.noToolsUsed() and formatResponse.missingToolParameterError() to accept optional hasMcpTools parameter
- Added getToolUseInstructionsReminder() function that provides correct syntax based on tool type
- When MCP tools are available, shows proper use_mcp_tool format instead of generic XML format
- Added hasMcpToolsAvailable() method to Task class to detect when MCP servers with tools are connected
- Updated all call sites to pass MCP tool availability information
- Maintains backward compatibility with legacy toolUseInstructionsReminder constant

This prevents the infinite loop where AI tries wrong MCP syntax and gets corrected with wrong format.
This commit is contained in:
Roo Code 2025-06-19 21:22:43 +00:00
parent 2e2f83be60
commit ba0d081bf2
2 changed files with 73 additions and 12 deletions

View file

@ -18,23 +18,23 @@ export const formatResponse = {
rooIgnoreError: (path: string) =>
`Access to ${path} is blocked by the .rooignore file settings. You must try to continue in the task without using this file, or ask the user to update the .rooignore file.`,
noToolsUsed: () =>
noToolsUsed: (hasMcpTools?: boolean) =>
`[ERROR] You did not use a tool in your previous response! Please retry with a tool use.
${toolUseInstructionsReminder}
${getToolUseInstructionsReminder(hasMcpTools)}
# Next Steps
If you have completed the user's task, use the attempt_completion tool.
If you require additional information from the user, use the ask_followup_question tool.
Otherwise, if you have not completed the task and do not need additional information, then proceed with the next step of the task.
If you have completed the user's task, use the attempt_completion tool.
If you require additional information from the user, use the ask_followup_question tool.
Otherwise, if you have not completed the task and do not need additional information, then proceed with the next step of the task.
(This is an automated message, so do not respond to it conversationally.)`,
tooManyMistakes: (feedback?: string) =>
`You seem to be having trouble proceeding. The user has provided the following feedback to help guide you:\n<feedback>\n${feedback}\n</feedback>`,
missingToolParameterError: (paramName: string) =>
`Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${toolUseInstructionsReminder}`,
missingToolParameterError: (paramName: string, hasMcpTools?: boolean) =>
`Missing value for required parameter '${paramName}'. Please retry with complete response.\n\n${getToolUseInstructionsReminder(hasMcpTools)}`,
lineCountTruncationError: (actualLineCount: number, isNewFile: boolean, diffStrategyEnabled: boolean = false) => {
const truncationMessage = `Note: Your response may have been truncated because it exceeded your output limit. You wrote ${actualLineCount} lines of content, but the line_count parameter was either missing or not included in your response.`
@ -66,7 +66,7 @@ Otherwise, if you have not completed the task and do not need additional informa
`RECOMMENDED APPROACH:\n` +
`${existingFileApproaches.join("\n")}\n`
return `${isNewFile ? newFileGuidance : existingFileGuidance}\n${toolUseInstructionsReminder}`
return `${isNewFile ? newFileGuidance : existingFileGuidance}\n${getToolUseInstructionsReminder()}`
},
invalidMcpToolArgumentError: (serverName: string, toolName: string) =>
@ -190,7 +190,34 @@ const formatImagesIntoBlocks = (images?: string[]): Anthropic.ImageBlockParam[]
: []
}
const toolUseInstructionsReminder = `# Reminder: Instructions for Tool Use
const getToolUseInstructionsReminder = (hasMcpTools?: boolean) => {
if (hasMcpTools) {
return `# Reminder: Instructions for MCP Tool Use
MCP tools must be called using the use_mcp_tool format:
<use_mcp_tool>
<server_name>server name here</server_name>
<tool_name>tool name here</tool_name>
<arguments>
{
"param1": "value1",
"param2": "value2"
}
</arguments>
</use_mcp_tool>
For regular tools, use the standard XML format:
<actual_tool_name>
<parameter1_name>value1</parameter1_name>
<parameter2_name>value2</parameter2_name>
...
</actual_tool_name>
Always use the correct format for the type of tool you are calling.`
} else {
return `# Reminder: Instructions for Tool Use
Tool uses are formatted using XML-style tags. The tool name itself becomes the XML tag name. Each parameter is enclosed within its own set of tags. Here's the structure:
@ -209,3 +236,8 @@ I have completed the task...
</attempt_completion>
Always use the actual tool name as the XML tag name for proper parsing and execution.`
}
}
// Legacy constant for backward compatibility
const toolUseInstructionsReminder = getToolUseInstructionsReminder()

View file

@ -709,7 +709,8 @@ export class Task extends EventEmitter<ClineEvents> {
relPath ? ` for '${relPath.toPosix()}'` : ""
} without value for required parameter '${paramName}'. Retrying...`,
)
return formatResponse.toolError(formatResponse.missingToolParameterError(paramName))
const hasMcpTools = await this.hasMcpToolsAvailable()
return formatResponse.toolError(formatResponse.missingToolParameterError(paramName, hasMcpTools))
}
// Start / Abort / Resume
@ -1128,7 +1129,8 @@ export class Task extends EventEmitter<ClineEvents> {
// the user hits max requests and denies resetting the count.
break
} else {
nextUserContent = [{ type: "text", text: formatResponse.noToolsUsed() }]
const hasMcpTools = await this.hasMcpToolsAvailable()
nextUserContent = [{ type: "text", text: formatResponse.noToolsUsed(hasMcpTools) }]
this.consecutiveMistakeCount++
}
}
@ -1517,7 +1519,8 @@ export class Task extends EventEmitter<ClineEvents> {
const didToolUse = this.assistantMessageContent.some((block) => block.type === "tool_use")
if (!didToolUse) {
this.userMessageContent.push({ type: "text", text: formatResponse.noToolsUsed() })
const hasMcpTools = await this.hasMcpToolsAvailable()
this.userMessageContent.push({ type: "text", text: formatResponse.noToolsUsed(hasMcpTools) })
this.consecutiveMistakeCount++
}
@ -1622,6 +1625,32 @@ export class Task extends EventEmitter<ClineEvents> {
})()
}
private async hasMcpToolsAvailable(): Promise<boolean> {
try {
const { mcpEnabled } = (await this.providerRef.deref()?.getState()) ?? {}
if (!(mcpEnabled ?? true)) {
return false
}
const provider = this.providerRef.deref()
if (!provider) {
return false
}
const mcpHub = await McpServerManager.getInstance(provider.context, provider)
if (!mcpHub) {
return false
}
// Check if there are any connected MCP servers with tools
const servers = mcpHub.getServers()
return servers.some((server) => server.tools && server.tools.length > 0)
} catch (error) {
console.error("Error checking MCP tools availability:", error)
return false
}
}
public async *attemptApiRequest(retryAttempt: number = 0): ApiStream {
const state = await this.providerRef.deref()?.getState()
const {