Skip to content

Commit b610b3a

Browse files
committed
Fixed Code Escaping for MCP Tools
1 parent ce3e4e8 commit b610b3a

File tree

2 files changed

+122
-10
lines changed

2 files changed

+122
-10
lines changed

src/core/tools/useMcpToolTool.ts

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,63 @@ export async function useMcpToolTool(
4444

4545
if (mcp_arguments) {
4646
try {
47+
// First try to parse as JSON directly
4748
parsedArguments = JSON.parse(mcp_arguments)
4849
} catch (error) {
49-
cline.consecutiveMistakeCount++
50-
cline.recordToolError("use_mcp_tool")
51-
await cline.say("error", `Roo tried to use ${tool_name} with an invalid JSON argument. Retrying...`)
52-
53-
pushToolResult(
54-
formatResponse.toolError(formatResponse.invalidMcpToolArgumentError(server_name, tool_name)),
55-
)
56-
57-
return
50+
// If direct parsing fails, try to handle it as a raw string that might need escaping
51+
try {
52+
// Check if it looks like a JSON object already (starts with { or [)
53+
const trimmed = mcp_arguments.trim()
54+
if (
55+
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
56+
(trimmed.startsWith("[") && trimmed.endsWith("]"))
57+
) {
58+
// If it looks like JSON but couldn't be parsed, then it's truly invalid
59+
cline.consecutiveMistakeCount++
60+
cline.recordToolError("use_mcp_tool")
61+
await cline.say(
62+
"error",
63+
`Roo tried to use ${tool_name} with an invalid JSON argument. Retrying...`,
64+
)
65+
66+
pushToolResult(
67+
formatResponse.toolError(
68+
formatResponse.invalidMcpToolArgumentError(server_name, tool_name),
69+
),
70+
)
71+
return
72+
}
73+
74+
// Otherwise, handle it as a raw string input - automatically place it in a properly escaped JSON object
75+
// This assumes the MCP tool expects an object with an 'input_data' field
76+
parsedArguments = {
77+
input_data: mcp_arguments,
78+
}
79+
80+
console.log("Auto-escaped code for MCP tool:", tool_name)
81+
} catch (nestedError) {
82+
cline.consecutiveMistakeCount++
83+
cline.recordToolError("use_mcp_tool")
84+
await cline.say("error", `Failed to process arguments for ${tool_name}. Please try again.`)
85+
86+
pushToolResult(
87+
formatResponse.toolError(
88+
formatResponse.invalidMcpToolArgumentError(server_name, tool_name),
89+
),
90+
)
91+
return
92+
}
5893
}
5994
}
6095

6196
cline.consecutiveMistakeCount = 0
6297

98+
// Create the approval message with the properly formatted arguments
6399
const completeMessage = JSON.stringify({
64100
type: "use_mcp_tool",
65101
serverName: server_name,
66102
toolName: tool_name,
67-
arguments: mcp_arguments,
103+
arguments: parsedArguments ? JSON.stringify(parsedArguments) : mcp_arguments,
68104
} satisfies ClineAskUseMcpServer)
69105

70106
const didApprove = await askApproval("use_mcp_server", completeMessage)

src/test-mcp-escaping.js

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Test script for MCP code escaping
2+
// This script tests the code escaping functionality for MCP tools
3+
// It simulates the behavior of useMcpToolTool.ts with different types of input
4+
5+
function testMcpEscaping(input, description) {
6+
console.log(`\n=== Testing ${description} ===`);
7+
console.log("Input:", input);
8+
9+
try {
10+
// First try to parse as JSON directly (normal behavior)
11+
const parsed = JSON.parse(input);
12+
console.log("✅ Successfully parsed as JSON:", parsed);
13+
return parsed;
14+
} catch (error) {
15+
console.log("❌ Failed to parse as JSON, trying fallback handling...");
16+
17+
try {
18+
// Check if it looks like a JSON object already (starts with { or [)
19+
const trimmed = input.trim();
20+
if (
21+
(trimmed.startsWith("{") && trimmed.endsWith("}")) ||
22+
(trimmed.startsWith("[") && trimmed.endsWith("]"))
23+
) {
24+
// If it looks like JSON but couldn't be parsed, then it's truly invalid
25+
console.log("❌ Input appears to be invalid JSON");
26+
return null;
27+
}
28+
29+
// Otherwise, handle it as a raw string input - automatically place it in a properly escaped JSON object
30+
const parsedArguments = {
31+
input_data: input,
32+
};
33+
console.log("✅ Handled as raw string input:", parsedArguments);
34+
return parsedArguments;
35+
} catch (nestedError) {
36+
console.log("❌ Failed to process arguments:", nestedError);
37+
return null;
38+
}
39+
}
40+
}
41+
42+
// Test cases
43+
console.log("TESTING MCP CODE ESCAPING");
44+
45+
// Test 1: Valid JSON
46+
testMcpEscaping('{"name": "test", "value": 123}', "Valid JSON");
47+
48+
// Test 2: Invalid JSON
49+
testMcpEscaping('{name: "test", value: 123}', "Invalid JSON");
50+
51+
// Test 3: LaTeX code with backslashes and special characters
52+
testMcpEscaping(`\\stepcounter{fragenummer}
53+
\\arabic{fragenummer}. & \\multicolumn{1}{|p{12cm}|}{\\raggedright #1 } & \\ifthenelse{#2=1}{{
54+
\\color{blue}{X}
55+
}}{} & \\ifthenelse{#2=1}{}{
56+
\\color{blue}{X}
57+
}`, "LaTeX code with special characters");
58+
59+
// Test 4: Python code with indentation, quotes and backslashes
60+
testMcpEscaping(`def process_string(s):
61+
# Handle escape sequences
62+
s = s.replace('\\n', '\\\\n')
63+
s = s.replace('\\t', '\\\\t')
64+
65+
# Handle quotes
66+
s = s.replace('"', '\\"')
67+
s = s.replace("'", "\\'")
68+
69+
return f"Processed: {s}"
70+
71+
print(process_string("Hello\\nWorld"))`, "Python code with quotes and backslashes");
72+
73+
// Test 5: Plain text
74+
testMcpEscaping("This is just plain text without any special formatting", "Plain text");
75+
76+
console.log("\nAll tests completed!");

0 commit comments

Comments
 (0)