Skip to content

Commit 5661734

Browse files
committed
fix(mcp): ensure tool_result blocks for all tool_use blocks to prevent API errors
Fixes critical issue where tool_use blocks could be orphaned without corresponding tool_result blocks, causing Claude API 400 errors: "tool_use ids were found without tool_result blocks immediately after" Changes: - Collect tool results atomically before adding to messages - Add error recovery to append tool_result for any orphaned tool_use blocks - Ensures 1:1 pairing of tool_use and tool_result even on exceptions Location: hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py:449-603
1 parent ef545d8 commit 5661734

File tree

1 file changed

+43
-2
lines changed

1 file changed

+43
-2
lines changed

pkg/hanzo-mcp/hanzo_mcp/tools/agent/agent_tool_v1_deprecated.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -450,6 +450,9 @@ async def _execute_agent_with_tools(
450450
tool_call_count = len(message.tool_calls)
451451
await tool_ctx.info(f"Processing {tool_call_count} tool calls")
452452

453+
# Track which tool calls we've processed to ensure all get results
454+
tool_results_to_add = []
455+
453456
for tool_call in message.tool_calls:
454457
total_tool_use_count += 1
455458
function_name = tool_call.function.name
@@ -542,8 +545,8 @@ async def _execute_agent_with_tools(
542545
await tool_ctx.info(
543546
f"tool {function_name} run with args {function_args} and return {tool_result[: min(100, len(tool_result))]}"
544547
)
545-
# Add the tool result to messages
546-
messages.append(
548+
# Add the tool result to the list
549+
tool_results_to_add.append(
547550
{
548551
"role": "tool",
549552
"tool_call_id": tool_call.id,
@@ -552,13 +555,51 @@ async def _execute_agent_with_tools(
552555
}
553556
)
554557

558+
# Add all tool results to messages atomically
559+
messages.extend(tool_results_to_add)
560+
555561
# Log progress
556562
await tool_ctx.info(f"Processed {len(message.tool_calls)} tool calls. Total: {total_tool_use_count}")
557563

558564
except Exception as e:
559565
await tool_ctx.error(f"Error in model call: {str(e)}")
560566
# Avoid trying to JSON serialize message objects
561567
await tool_ctx.error(f"Message count: {len(messages)}")
568+
569+
# CRITICAL FIX: Ensure we add tool_result messages for any tool_use blocks that were added
570+
# Get the last assistant message (which has the tool_calls)
571+
last_assistant_msg = None
572+
for msg in reversed(messages):
573+
if msg.get("role") == "assistant" and hasattr(msg, "tool_calls"):
574+
last_assistant_msg = msg
575+
break
576+
577+
# If there are orphaned tool calls, add error results for them
578+
if last_assistant_msg and hasattr(last_assistant_msg, "tool_calls"):
579+
# Count how many tool results we already have after this message
580+
tool_results_count = 0
581+
found_assistant = False
582+
for msg in reversed(messages):
583+
if msg == last_assistant_msg:
584+
found_assistant = True
585+
break
586+
if found_assistant and msg.get("role") == "tool":
587+
tool_results_count += 1
588+
589+
# Add error results for any missing tool calls
590+
expected_results = len(last_assistant_msg.tool_calls)
591+
if tool_results_count < expected_results:
592+
for i in range(tool_results_count, expected_results):
593+
tool_call = last_assistant_msg.tool_calls[i]
594+
messages.append(
595+
{
596+
"role": "tool",
597+
"tool_call_id": tool_call.id,
598+
"name": tool_call.function.name,
599+
"content": f"Error: Tool execution interrupted - {str(e)}",
600+
}
601+
)
602+
562603
return f"Error in agent execution: {str(e)}"
563604

564605
# If we've reached the limit, add a warning and get final response

0 commit comments

Comments
 (0)