Skip to content

Commit 8f103ff

Browse files
RoyLinRoyLin
authored andcommitted
fix: handle empty tool arguments for no-argument MCP tools
When the LLM calls a tool with no required arguments, it sends an empty argument string. serde_json::from_str("") fails with "EOF while parsing a value at line 1 column 0", which produced a __parse_error sentinel and incorrectly incremented the malformed-args counter, eventually aborting the agent after max_parse_retries. Fix: check for empty/whitespace argument strings before JSON parsing and default to {} (empty object). Applied to both the Anthropic streaming path (where the bug manifested as __parse_error) and the OpenAI paths (non-streaming and streaming, where the fallback was null instead of {}). Closes #13
1 parent 03985b0 commit 8f103ff

2 files changed

Lines changed: 42 additions & 28 deletions

File tree

core/src/llm/anthropic.rs

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -358,20 +358,26 @@ impl LlmClient for AnthropicClient {
358358
},
359359
AnthropicStreamEvent::ContentBlockStop { index: _ } => {
360360
if !current_tool_id.is_empty() {
361-
let input: serde_json::Value =
362-
serde_json::from_str(&current_tool_input)
363-
.unwrap_or_else(|e| {
364-
tracing::warn!(
365-
"Failed to parse tool input JSON for tool '{}': {}",
366-
current_tool_name, e
367-
);
368-
serde_json::json!({
369-
"__parse_error": format!(
370-
"Malformed tool arguments: {}. Raw input: {}",
371-
e, &current_tool_input
372-
)
361+
let input: serde_json::Value = if current_tool_input
362+
.trim()
363+
.is_empty()
364+
{
365+
serde_json::Value::Object(Default::default())
366+
} else {
367+
serde_json::from_str(&current_tool_input)
368+
.unwrap_or_else(|e| {
369+
tracing::warn!(
370+
"Failed to parse tool input JSON for tool '{}': {}",
371+
current_tool_name, e
372+
);
373+
serde_json::json!({
374+
"__parse_error": format!(
375+
"Malformed tool arguments: {}. Raw input: {}",
376+
e, &current_tool_input
377+
)
378+
})
373379
})
374-
});
380+
};
375381
content_blocks.push(ContentBlock::ToolUse {
376382
id: current_tool_id.clone(),
377383
name: current_tool_name.clone(),

core/src/llm/openai.rs

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -280,14 +280,18 @@ impl LlmClient for OpenAiClient {
280280
content.push(ContentBlock::ToolUse {
281281
id: tc.id,
282282
name: tc.function.name.clone(),
283-
input: serde_json::from_str(&tc.function.arguments).unwrap_or_else(|e| {
284-
tracing::warn!(
285-
"Failed to parse tool arguments JSON for tool '{}': {}",
286-
tc.function.name,
287-
e
288-
);
289-
serde_json::Value::default()
290-
}),
283+
input: if tc.function.arguments.trim().is_empty() {
284+
serde_json::Value::Object(Default::default())
285+
} else {
286+
serde_json::from_str(&tc.function.arguments).unwrap_or_else(|e| {
287+
tracing::warn!(
288+
"Failed to parse tool arguments JSON for tool '{}': {}",
289+
tc.function.name,
290+
e
291+
);
292+
serde_json::Value::Object(Default::default())
293+
})
294+
},
291295
});
292296
}
293297
}
@@ -445,13 +449,17 @@ impl LlmClient for OpenAiClient {
445449
content_blocks.push(ContentBlock::ToolUse {
446450
id: id.clone(),
447451
name: name.clone(),
448-
input: serde_json::from_str(args).unwrap_or_else(|e| {
449-
tracing::warn!(
450-
"Failed to parse tool arguments JSON for tool '{}': {}",
451-
name, e
452-
);
453-
serde_json::Value::default()
454-
}),
452+
input: if args.trim().is_empty() {
453+
serde_json::Value::Object(Default::default())
454+
} else {
455+
serde_json::from_str(args).unwrap_or_else(|e| {
456+
tracing::warn!(
457+
"Failed to parse tool arguments JSON for tool '{}': {}",
458+
name, e
459+
);
460+
serde_json::Value::Object(Default::default())
461+
})
462+
},
455463
});
456464
}
457465
tool_calls.clear();

0 commit comments

Comments
 (0)