Skip to content

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

Closed as not planned

Description

@Robin1987China

Bug Description

validate_tool_use_result_messages() in src/mcp/server/validation.py:83-87 raises a false ValueError when a tool_use block in the previous message is not followed by a tool_result block in the last message.

Spec Context

Per SEP-1577:

  • tool_result blocks MUST be preceded by a tool_use block ✅ (already checked)
  • There is no requirement that tool_use must be followed by tool_result

A plain text response after a tool_use is valid.

Reproduction

from mcp.types import SamplingMessage, TextContent, ToolUseContent
from mcp.server.validation import validate_tool_use_result_messages

msg1 = SamplingMessage(role="assistant", content=[
    TextContent(type="text", text="Hold on..."),
    ToolUseContent(type="tool_use", id="abc", name="search", input={"q": "test"}),
])
msg2 = SamplingMessage(role="user", content=[
    TextContent(type="text", text="Thanks, no results needed"),
])

validate_tool_use_result_messages([msg1, msg2])
# ValueError: ids of tool_result blocks and tool_use blocks from previous message do not match
# This is a false positive — msg2 has NO tool_result at all!

Root Cause

Line 83 runs the ID-matching check outside the if has_tool_results: block:

# L83 — runs unconditionally after the has_tool_results block
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 is empty → mismatch
        raise ValueError(...)

Fix (proposed)

Add has_tool_results to the condition:

if has_tool_results and has_previous_tool_use and previous_content:

AI Disclosure

Bug discovered with AI assistance (opencode).

Activity

  1. added
    v2Affects the v2 line (2.x on main)
    on Jun 30, 2026
  2. Sehlani042 commented on Jul 2, 2026

    @Sehlani042

    I checked this against the current draft sampling spec and SEP-1577 before opening a PR. It looks like the current validator is enforcing the documented tool-use/result balance rule rather than a false positive.

    The draft spec's “Tool Use and Result Balance” section says an assistant message with ToolUseContent must be followed by a user message made of matching ToolResultContent blocks before any other message. SEP-1577 has the same balance requirement for CreateMessageRequest.messages.

    Links:

    I can see one possible small improvement: if maintainers want a clearer diagnostic, the SDK could distinguish “previous assistant tool_use was not followed by any tool_result message” from the current generic id-mismatch error. But accepting plain text after an unresolved tool_use seems contrary to the current sampling spec.

  3. tarunag10 commented on Jul 3, 2026

    @tarunag10

    I prepared a focused fix branch for this:

    https://github.com/tarunag10/python-sdk/tree/fix-tool-use-result-validation

    What it changes:

    • validate_tool_use_result_messages() now returns early when the final sampling message has no tool_result content, so a prior tool_use is not treated as requiring the next message to be a result.
    • Added a regression test for the reported case: assistant text + tool_use, followed by ordinary user text.

    Validation run locally:

    • UV_CACHE_DIR=/private/tmp/uv-cache uv run pytest tests/server/test_validation.py -q
    • UV_CACHE_DIR=/private/tmp/uv-cache uv run ruff check src/mcp/server/validation.py tests/server/test_validation.py
    • UV_CACHE_DIR=/private/tmp/uv-cache uv run ruff format --check src/mcp/server/validation.py tests/server/test_validation.py

    I tried opening the upstream PR from the fork, but GitHub returned a repository permission error for the PR-create mutation/API from this account. Happy to reopen if permissions change; the branch is ready for review.

  4. STiFLeR7 commented on Jul 10, 2026

    @STiFLeR7

    Reviewed all three open PRs against validate_tool_use_result_messages — worth flagging that they are not three implementations of the same fix. They represent two mutually exclusive policy answers to the question @Sehlani042 raised:

    #3053 takes the position that the current behavior is spec-correct (tool_use must always be followed by a matching tool_result) and just gives it a clearer, dedicated error message ("tool_use blocks must be followed by matching tool_result blocks") instead of the confusing generic id-mismatch message — it does not change what gets rejected.

    #2981 and #2962 take the opposite position — both gate the id-matching check on has_tool_results, so a plain-text message following an unresolved tool_use is now accepted rather than rejected. I traced through both diffs: despite looking different (#2981 removes the inner if has_previous_tool_use and previous_content: guard and de-indents its body into the outer has_tool_results block, #2962 adds an explicit if has_tool_results and has_previous_tool_use and previous_content:), they are semantically identical in effect once you account for the earlier if not has_previous_tool_use: raise check that already guarantees both conditions are true by that point. #2962 is the more minimal/readable diff of the two if this is the direction taken.

    Given the docstring for this function documents its own rules (2) and (3) as being about "tool_result messages" specifically (i.e. conditioned on has_tool_results), the current code is inconsistent with its own stated contract regardless of which spec interpretation is correct — that part is an objective bug either way. But whether the fix should be "relax the check" (#2981/#2962) or "keep enforcing it, just with a clearer error" (#3053) depends entirely on resolving whether SEP-1577 actually mandates the balance requirement unconditionally, which is a maintainer/spec-owner call, not something the PRs themselves can settle. Might be worth resolving that question explicitly before merging any of the three, since merging the wrong one is a real spec-compliance regression in either direction.

  5. maxisbey commented on Aug 14, 2026

    @maxisbey
    Contributor

    The check here is enforcing the spec rather than misfiring: the sampling spec's Tool Use and Result Balance section, and SEP-1577 itself, say every assistant message containing ToolUseContent MUST be followed by a user message consisting entirely of matching ToolResultContent blocks before any other message, so a text-only turn after an unresolved tool_use is invalid input to create_message and the validator is right to reject it. I agree the generic id-mismatch error is a confusing way to say that, and #3053 tracks giving this case a dedicated message. Feel free to reopen if the spec wording changes or if there's a case here I've misread.

    AI Disclaimer

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    v2Affects the v2 line (2.x on main)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions