Repository navigation
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 |
|
Thanks for the PR, and sorry it sat here without a proper review. We're closing most of the open PR backlog. v2 is out and changed a lot of the SDK, so many older PRs no longer apply as written, and we're a small team that realistically doesn't have the capacity to work through the rest. If this still matters to you on v2, the most useful thing you can do is open an issue (or comment on the existing one) with your use case and a repro. Hearing why it matters to you is what we use to decide what to prioritise. |
|
Thanks for the note, and no worries on the wait. Understood that v2 moved and the backlog is being closed rather than reviewed in place. I won't reopen this PR. The original case (tool_use not followed by a tool_result raising a false ValueError, contrary to SEP-1577) is already tracked on #2960. If that still reproduces on v2 I'll add a short repro there; if v2 already dropped or fixed the check, I'll leave it. |
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: