fix(server): don't raise when tool_use is not followed by tool_result#2962
fix(server): don't raise when tool_use is not followed by tool_result#2962Bartok9 wants to merge 1 commit into
Conversation
Closes modelcontextprotocol#2960 Per SEP-1577, tool_result blocks MUST be preceded by a tool_use block, but there is no requirement that a tool_use MUST be followed by a tool_result. A plain text response after a tool_use is valid. The ID-matching check in validate_tool_use_result_messages ran whenever the previous message contained a tool_use, regardless of whether the last message actually had any tool_result blocks. With no tool_result blocks present, tool_result_ids was empty and never matched the non-empty tool_use_ids, raising a false 'ids ... do not match' ValueError. Gate the check on has_tool_results so it only runs when the last message actually contains tool_result blocks. Adds a regression test that fails without the fix.
Robin1987China
left a comment
There was a problem hiding this comment.
Fix is correct and minimal. The one-line change (has_tool_results and ...) correctly gates ID matching to only run when the last message actually contains tool_result blocks. The regression test reproduces the exact case from #2960. All 16 existing tests still pass.
The test could be slightly simplified (the TextContent(type="text", text="Hold on...") in the assistant message doesn't affect the scenario), but it's not incorrect — it's more realistic.
AI assistance disclosure: Reviewed with AI assistance (opencode). I have verified the fix logic against the SEP-1577 spec.
|
Thanks for the careful review, @Robin1987China — glad the gating logic and regression test hold up. Good eye on the |
Summary
validate_tool_use_result_messages()no longer raises a falseValueErrorwhen atool_useblock is followed by a plain (non-tool_result) response.Motivation
Closes #2960.
Per SEP-1577:
tool_resultblocks MUST be preceded by atool_useblock ✅ (already checked)tool_useMUST be followed by atool_result.The ID-matching block at the end of the function ran whenever the previous message contained a
tool_use, regardless of whether the last message actually contained anytool_resultblocks:When the last message has no
tool_resultblocks,tool_result_idsis empty and never equals the non-emptytool_use_ids, so a valid conversation raisedValueError: ids of tool_result blocks and tool_use blocks from previous message do not match.Fix
Gate the ID-matching check on
has_tool_resultsso it only runs when the last message actually containstool_resultblocks:This is a one-line change; all existing structural checks (mixed content, missing preceding tool_use, ID mismatch when results ARE present) are unaffected.
Verification
uv run pytest tests/server/test_validation.py— 16 passeduv run ruff check/ruff format --check— cleanReal behavior proof
Regression test
test_validate_tool_use_result_messages_no_error_when_tool_use_not_followed_by_tool_resultreproduces the exact case from the issue (assistant emits text + tool_use; user replies with plain text). It fails without the fix and passes with it: