Skip to content

Commit 2732dcb

Browse files
daniel-lxsMy Name
authored andcommitted
Replace hyphen encoding with fuzzy matching for MCP tool names (RooCodeInc#10775)
1 parent 2620de6 commit 2732dcb

5 files changed

Lines changed: 303 additions & 159 deletions

File tree

src/core/prompts/tools/native-tools/__tests__/mcp_server.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ describe("getMcpServerTools", () => {
8989

9090
// Should only have one tool (from project server)
9191
expect(result).toHaveLength(1)
92-
expect(getFunction(result[0]).name).toBe("mcp--context7--resolve___library___id")
92+
expect(getFunction(result[0]).name).toBe("mcp--context7--resolve-library-id")
9393
// Project server takes priority
9494
expect(getFunction(result[0]).description).toBe("Project description")
9595
})

src/core/tools/UseMcpToolTool.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { Task } from "../task/Task"
44
import { formatResponse } from "../prompts/responses"
55
import { t } from "../../i18n"
66
import type { ToolUse } from "../../shared/tools"
7+
import { toolNamesMatch } from "../../utils/mcp-name"
78

89
import { BaseTool, ToolCallbacks } from "./BaseTool"
910

@@ -43,14 +44,18 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> {
4344
return
4445
}
4546

47+
// Use the resolved tool name (original name from the server) for MCP calls
48+
// This handles cases where models mangle hyphens to underscores
49+
const resolvedToolName = toolValidation.resolvedToolName ?? toolName
50+
4651
// Reset mistake count on successful validation
4752
task.consecutiveMistakeCount = 0
4853

4954
// Get user approval
5055
const completeMessage = JSON.stringify({
5156
type: "use_mcp_tool",
5257
serverName,
53-
toolName,
58+
toolName: resolvedToolName,
5459
arguments: params.arguments ? JSON.stringify(params.arguments) : undefined,
5560
} satisfies ClineAskUseMcpServer)
5661

@@ -65,7 +70,7 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> {
6570
await this.executeToolAndProcessResult(
6671
task,
6772
serverName,
68-
toolName,
73+
resolvedToolName,
6974
parsedArguments,
7075
executionId,
7176
pushToolResult,
@@ -137,7 +142,7 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> {
137142
serverName: string,
138143
toolName: string,
139144
pushToolResult: (content: string) => void,
140-
): Promise<{ isValid: boolean; availableTools?: string[] }> {
145+
): Promise<{ isValid: boolean; availableTools?: string[]; resolvedToolName?: string }> {
141146
try {
142147
// Get the MCP hub to access server information
143148
const provider = task.providerRef.deref()
@@ -186,8 +191,8 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> {
186191
return { isValid: false, availableTools: [] }
187192
}
188193

189-
// Check if the requested tool exists
190-
const tool = server.tools.find((tool) => tool.name === toolName)
194+
// Check if the requested tool exists (using fuzzy matching to handle model mangling of hyphens)
195+
const tool = server.tools.find((t) => toolNamesMatch(t.name, toolName))
191196

192197
if (!tool) {
193198
// Tool not found - provide list of available tools
@@ -232,8 +237,8 @@ export class UseMcpToolTool extends BaseTool<"use_mcp_tool"> {
232237
return { isValid: false, availableTools: enabledToolNames }
233238
}
234239

235-
// Tool exists and is enabled
236-
return { isValid: true, availableTools: server.tools.map((tool) => tool.name) }
240+
// Tool exists and is enabled - return the original tool name for use with the MCP server
241+
return { isValid: true, availableTools: server.tools.map((t) => t.name), resolvedToolName: tool.name }
237242
} catch (error) {
238243
// If there's an error during validation, log it but don't block the tool execution
239244
// The actual tool call might still fail with a proper error

src/services/mcp/McpHub.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ import { fileExistsAtPath } from "../../utils/fs"
3838
import { arePathsEqual, getWorkspacePath } from "../../utils/path"
3939
import { injectVariables } from "../../utils/config"
4040
import { safeWriteJson } from "../../utils/safeWriteJson"
41-
import { sanitizeMcpName } from "../../utils/mcp-name"
41+
import { sanitizeMcpName, toolNamesMatch } from "../../utils/mcp-name"
4242

4343
// Discriminated union for connection states
4444
export type ConnectedMcpConnection = {
@@ -940,16 +940,30 @@ export class McpHub {
940940
* Find a connection by sanitized server name.
941941
* This is used when parsing MCP tool responses where the server name has been
942942
* sanitized (e.g., hyphens replaced with underscores) for API compliance.
943+
* Uses fuzzy matching to handle cases where models convert hyphens to underscores.
943944
* @param sanitizedServerName The sanitized server name from the API tool call
944945
* @returns The original server name if found, or null if no match
945946
*/
946947
public findServerNameBySanitizedName(sanitizedServerName: string): string | null {
948+
// First, check for an exact match
947949
const exactMatch = this.connections.find((conn) => conn.server.name === sanitizedServerName)
948950
if (exactMatch) {
949951
return exactMatch.server.name
950952
}
951953

952-
return this.sanitizedNameRegistry.get(sanitizedServerName) ?? null
954+
// Check the registry for sanitized name mapping
955+
const registryMatch = this.sanitizedNameRegistry.get(sanitizedServerName)
956+
if (registryMatch) {
957+
return registryMatch
958+
}
959+
960+
// Use fuzzy matching: treat hyphens and underscores as equivalent
961+
const fuzzyMatch = this.connections.find((conn) => toolNamesMatch(conn.server.name, sanitizedServerName))
962+
if (fuzzyMatch) {
963+
return fuzzyMatch.server.name
964+
}
965+
966+
return null
953967
}
954968

955969
private async fetchToolsList(serverName: string, source?: "global" | "project"): Promise<McpTool[]> {

0 commit comments

Comments
 (0)