Skip to content

fix(server): don't raise when tool_use is not followed by tool_result#2962

Open
Bartok9 wants to merge 1 commit into
modelcontextprotocol:mainfrom
Bartok9:fix/2960-tool-use-validation-false-positive
Open

fix(server): don't raise when tool_use is not followed by tool_result#2962
Bartok9 wants to merge 1 commit into
modelcontextprotocol:mainfrom
Bartok9:fix/2960-tool-use-validation-false-positive

Conversation

@Bartok9

@Bartok9 Bartok9 commented Jun 25, 2026

Copy link
Copy Markdown

Summary

  • validate_tool_use_result_messages() no longer raises a false ValueError when a tool_use block is followed by a plain (non-tool_result) response.
  • A model that calls a tool and then replies with text (or the user declining further results) is now accepted, matching SEP-1577.

Motivation

Closes #2960.

Per SEP-1577:

  • tool_result blocks MUST be preceded by a tool_use block ✅ (already checked)
  • There is no requirement that a tool_use MUST be followed by a tool_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 any tool_result blocks:

if 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:  # tool_result_ids empty -> mismatch
        raise ValueError(...)

When the last message has no tool_result blocks, tool_result_ids is empty and never equals the non-empty tool_use_ids, so a valid conversation raised ValueError: ids of tool_result blocks and tool_use blocks from previous message do not match.

Fix

Gate the ID-matching check on has_tool_results so it only runs when the last message actually contains tool_result blocks:

if has_tool_results and has_previous_tool_use and previous_content:

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 passed
  • uv run ruff check / ruff format --check — clean

Real behavior proof

Regression test test_validate_tool_use_result_messages_no_error_when_tool_use_not_followed_by_tool_result reproduces 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:

# Without the source fix (test present):
tests/server/test_validation.py::...not_followed_by_tool_result  164 / 185  ValueError
Results: 1 failed

# With the fix:
Results: 16 passed

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 Robin1987China left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@Bartok9

Bartok9 commented Jun 25, 2026

Copy link
Copy Markdown
Author

Thanks for the careful review, @Robin1987China — glad the gating logic and regression test hold up. Good eye on the TextContent("Hold on...") line; I left it in deliberately to mirror a realistic model turn (text + declining further tool results), but I'm happy to trim it to the minimal repro if maintainers prefer the leaner version. Either way works for me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] validate_tool_use_result_messages raises false positive when tool_use is not followed by tool_result

2 participants