Repository navigation
AnthropicLlm sends failed MCP tool results to Claude with is_error=False聽#7664
Description
馃敶 Required Information
Describe the Bug:
When an MCP tool call fails, Claude is told it succeeded. The MCP server returns a CallToolResult with isError: true, and McpTool passes that result on as the function response. AnthropicLlm then builds the tool_result block with is_error=False hardcoded (_part_to_message_block in src/google/adk/models/anthropic_llm.py). Claude receives the error text as a successful result, so it may treat the error message as data instead of recovering from the failure.
McpTool._detect_error_in_response already reads the same flag (both isError and is_error) for telemetry, so ADK knows the call failed; only the Anthropic request loses it.
Steps to Reproduce:
pip install google-adk==2.11.0 anthropic- Run the snippet below. It calls the conversion function directly, so no API key is needed.
Expected Behavior:
'file not found: notes.txt' is_error = True
Observed Behavior:
'file not found: notes.txt' is_error = False
Environment Details:
- ADK Library Version: 2.11.0 (also reproduced on
mainat d324744) - Desktop OS: macOS
- Python Version: 3.12.13
Model Information:
- Are you using LiteLLM: No
- Which model is being used: Claude via
AnthropicLlm/Claude
馃煛 Optional Information
Minimal Reproduction Code:
from google.adk.models.anthropic_llm import part_to_message_block
from google.genai import types
# What McpTool returns when an MCP server reports a failed call.
part = types.Part.from_function_response(
name="read_file",
response={
"content": [{"type": "text", "text": "file not found: notes.txt"}],
"isError": True,
},
)
part.function_response.id = "toolu_1"
block = part_to_message_block(part)
print(repr(block["content"]), "is_error =", block["is_error"])How often has this issue occurred?:
- Always (100%)
Proposed fix: Set is_error from the response's isError (or is_error) flag instead of hardcoding False. I have a small PR ready with unit tests.
A related question (not part of that PR): when a tool result has a content key, _part_to_message_block sends only that value and drops every other key. That seems intended for load_skill_resource (there is a test asserting its skill_name and file_path are dropped). For other tools it loses data, for example {"content": ["a", "b"], "next_page_token": "tok2"} reaches Claude as "a\nb", without the pagination token. Would you like that narrowed (for example, only take the shortcut for MCP results and load_skill_resource, and JSON-encode everything else, as the result branch does since #7073)? I'm happy to file it separately if so.
Activity
fix: mark failed MCP tool results as errors for Claude