Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit 4b40ed2

Browse files
committed
fix: preserve triple underscores in MCP tool names
This fix addresses Issue #10858 where MCP aggregators (like 1mcp) use triple underscores (___) in tool names (e.g., playwright___browser_navigate) but Roo Code was incorrectly converting them to hyphens. The issue was in normalizeMcpToolName() which used the regex /__|--/ to split tool names. This regex matched double underscores anywhere, including within triple underscore sequences. The fix changes the regex to /(?<!_)__(?!_)|--/ which uses negative lookbehind/lookahead to match exactly two underscores that are not part of a longer underscore sequence. Changes: - Modified split regex in normalizeMcpToolName() to preserve ___ - Added comprehensive tests for triple underscore tool names - Added full flow test for MCP aggregator scenario
1 parent 953c777 commit 4b40ed2

2 files changed

Lines changed: 66 additions & 1 deletion

File tree

src/utils/__tests__/mcp-name.spec.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,22 @@ describe("mcp-name utilities", () => {
250250
expect(parseMcpToolName("mcp--")).toBeNull()
251251
expect(parseMcpToolName("mcp--server")).toBeNull()
252252
})
253+
254+
it("should preserve triple underscores in tool names (MCP aggregator format)", () => {
255+
// MCP aggregators like 1mcp use triple underscores to separate upstream server and tool names
256+
expect(parseMcpToolName("mcp--1mcp--playwright___browser_navigate")).toEqual({
257+
serverName: "1mcp",
258+
toolName: "playwright___browser_navigate",
259+
})
260+
})
261+
262+
it("should preserve triple underscores when model converts separators", () => {
263+
// Model converts -- to __, but triple underscores should be preserved
264+
expect(parseMcpToolName("mcp__1mcp__playwright___browser_navigate")).toEqual({
265+
serverName: "1mcp",
266+
toolName: "playwright___browser_navigate",
267+
})
268+
})
253269
})
254270

255271
describe("normalizeMcpToolName", () => {
@@ -278,6 +294,26 @@ describe("mcp-name utilities", () => {
278294
// Normalized: mcp--server--get_user_profile
279295
expect(normalizeMcpToolName("mcp__server__get_user_profile")).toBe("mcp--server--get_user_profile")
280296
})
297+
298+
it("should preserve triple underscores in tool names (MCP aggregator format)", () => {
299+
// MCP aggregators like 1mcp use triple underscores to separate upstream server and tool names
300+
// e.g., playwright___browser_navigate means "browser_navigate" tool from "playwright" upstream server
301+
expect(normalizeMcpToolName("mcp__1mcp__playwright___browser_navigate")).toBe(
302+
"mcp--1mcp--playwright___browser_navigate",
303+
)
304+
})
305+
306+
it("should preserve multiple triple underscore segments", () => {
307+
// Tool name with multiple triple underscore segments
308+
expect(normalizeMcpToolName("mcp__aggregator__server___tool___subtool")).toBe(
309+
"mcp--aggregator--server___tool___subtool",
310+
)
311+
})
312+
313+
it("should handle quadruple underscores correctly", () => {
314+
// Quadruple underscores should remain intact (not treated as separator)
315+
expect(normalizeMcpToolName("mcp__server__tool____name")).toBe("mcp--server--tool____name")
316+
})
281317
})
282318

283319
describe("roundtrip behavior", () => {
@@ -426,6 +462,33 @@ describe("mcp-name utilities", () => {
426462
// Use fuzzy matching to find the original tool
427463
expect(toolNamesMatch("get-user-profile", parsed!.toolName)).toBe(true)
428464
})
465+
466+
it("should handle MCP aggregator tool names with triple underscores (Issue #10858)", () => {
467+
// This test covers the bug reported in Issue #10858
468+
// MCP aggregators like 1mcp use triple underscores as separators
469+
// e.g., playwright___browser_navigate
470+
471+
// Step 1: Build the tool name with an MCP aggregator (using a letter-prefixed server name)
472+
const builtName = buildMcpToolName("aggregator", "playwright___browser_navigate")
473+
expect(builtName).toBe("mcp--aggregator--playwright___browser_navigate")
474+
475+
// Step 2: Model converts -- separators to __ (but preserves ___ in tool name)
476+
const modelOutput = "mcp__aggregator__playwright___browser_navigate"
477+
478+
// Step 3: Normalize - should preserve the triple underscores in tool name
479+
const normalizedName = normalizeMcpToolName(modelOutput)
480+
expect(normalizedName).toBe("mcp--aggregator--playwright___browser_navigate")
481+
482+
// Step 4: Parse - tool name should have triple underscores preserved
483+
const parsed = parseMcpToolName(normalizedName)
484+
expect(parsed).toEqual({
485+
serverName: "aggregator",
486+
toolName: "playwright___browser_navigate",
487+
})
488+
489+
// The tool name should match exactly what the MCP server expects
490+
expect(parsed!.toolName).toBe("playwright___browser_navigate")
491+
})
429492
})
430493

431494
describe("edge cases", () => {

src/utils/mcp-name.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ export function normalizeMcpToolName(toolName: string): string {
5252

5353
// First, try to parse assuming all separators are underscores
5454
// Pattern: mcp__server__tool or mcp__server__tool_with_underscores
55-
const parts = toolName.split(/__|--/)
55+
// Use lookbehind/lookahead to match exactly __ (not part of ___ or longer)
56+
// This preserves triple underscores (___) used by MCP aggregators like 1mcp
57+
const parts = toolName.split(/(?<!_)__(?!_)|--/)
5658

5759
if (parts.length >= 3 && parts[0].toLowerCase() === "mcp") {
5860
// Reconstruct with proper -- separators

0 commit comments

Comments
 (0)