From ca9f8de9065d4682ed472da09ccb396543d3ef80 Mon Sep 17 00:00:00 2001 From: Bartok9 Date: Thu, 25 Jun 2026 02:02:45 -0400 Subject: [PATCH] fix(server): don't raise when tool_use is not followed by tool_result Closes #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. --- src/mcp/server/validation.py | 2 +- tests/server/test_validation.py | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/mcp/server/validation.py b/src/mcp/server/validation.py index 08f5754f1e..7c9eb87f74 100644 --- a/src/mcp/server/validation.py +++ b/src/mcp/server/validation.py @@ -80,7 +80,7 @@ def validate_tool_use_result_messages(messages: list[SamplingMessage]) -> None: if not has_previous_tool_use: raise ValueError("tool_result blocks do not match any tool_use in the previous message") - if has_previous_tool_use and previous_content: + if has_tool_results and has_previous_tool_use and previous_content: tool_use_ids = {c.id for c in previous_content if c.type == "tool_use"} tool_result_ids = {c.tool_use_id for c in last_content if c.type == "tool_result"} if tool_use_ids != tool_result_ids: diff --git a/tests/server/test_validation.py b/tests/server/test_validation.py index 19f4eb1088..646fc4240e 100644 --- a/tests/server/test_validation.py +++ b/tests/server/test_validation.py @@ -159,3 +159,27 @@ def test_validate_tool_use_result_messages_no_error_when_tool_result_matches_too ), ] validate_tool_use_result_messages(messages) # Should not raise + + +def test_validate_tool_use_result_messages_no_error_when_tool_use_not_followed_by_tool_result() -> None: + """No error when a tool_use is followed by a plain (non-tool_result) response. + + Regression test for #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 and + must not raise a false "ids do not match" ValueError. + """ + messages = [ + SamplingMessage( + role="assistant", + content=[ + TextContent(type="text", text="Hold on..."), + ToolUseContent(type="tool_use", id="abc", name="search", input={"q": "test"}), + ], + ), + SamplingMessage( + role="user", + content=TextContent(type="text", text="Thanks, no results needed"), + ), + ] + validate_tool_use_result_messages(messages) # Should not raise