Description
Three related bugs in GoogleGenaiSamplingHandler's response conversion functions (_response_to_result_with_tools and _response_to_create_message_result).
Bug 1: Thought parts leak through as TextContent
google_genai.py:363-365 has a comment saying "Skip thought parts" but the code only checks if part.text: — which is truthy for thought parts too.
# Current code (line 363-365)
# Note: Skip thought parts from thinking_config - not relevant for MCP responses
if part.text:
content.append(TextContent(type="text", text=part.text))
When thinking_budget is set and Gemini returns thought parts, the internal reasoning text is converted to TextContent and appears as actual response content in the MCP message.
Fix: if part.text and not part.thought:
Bug 2: Thinking-only responses crash
When Gemini returns a response where ALL parts have thought=True (the model only thought but produced no output):
- Non-tool path (
_response_to_create_message_result): response.text returns None because Google SDK's _get_text() filters thought parts. The handler crashes with ValueError: No content in response: STOP.
- Tool path (
_response_to_result_with_tools): Once Bug 1 is fixed and thought parts are filtered, the content list will be empty, crashing with ValueError: No content in response from completion.
Bug 3: Safety-filtered responses produce unhelpful errors
When Gemini blocks a response (safety filter, recitation, etc.):
- Non-tool path includes
finish_reason: "No content in response: SAFETY" — somewhat helpful
- Tool path at line 379 does NOT include
finish_reason: just "No content in response from completion" — no indication of why
The tool-path error should include candidate.finish_reason so callers can distinguish between "model returned nothing" vs "response was blocked by safety filter".
Reproduction
All three bugs are reproducible without API calls:
from fastmcp.client.sampling.handlers.google_genai import (
_response_to_result_with_tools,
_response_to_create_message_result,
)
from google.genai.types import Candidate, Content, GenerateContentResponse, Part
# Bug 1: Thought parts leak as TextContent
response = GenerateContentResponse(candidates=[Candidate(
content=Content(parts=[Part(text="thinking...", thought=True)], role="model"),
finish_reason="STOP",
)])
result = _response_to_result_with_tools(response, "test")
assert result.content[0].text == "thinking..." # BUG: should have been filtered
# Bug 2: Thinking-only response crashes non-tool path
assert response.text is None # Google SDK filters thoughts in .text
_response_to_create_message_result(response, "test")
# ValueError: No content in response: FinishReason.STOP
# Bug 3: Safety-filtered response — unhelpful error on tool path
response = GenerateContentResponse(candidates=[Candidate(
content=Content(parts=[], role="model"),
finish_reason="SAFETY",
)])
_response_to_result_with_tools(response, "test")
# ValueError: "No content in response from completion" — no mention of SAFETY
Version
fastmcp 3.2.3, google-genai 1.72.0
Description
Three related bugs in
GoogleGenaiSamplingHandler's response conversion functions (_response_to_result_with_toolsand_response_to_create_message_result).Bug 1: Thought parts leak through as TextContent
google_genai.py:363-365has a comment saying "Skip thought parts" but the code only checksif part.text:— which is truthy for thought parts too.When
thinking_budgetis set and Gemini returns thought parts, the internal reasoning text is converted toTextContentand appears as actual response content in the MCP message.Fix:
if part.text and not part.thought:Bug 2: Thinking-only responses crash
When Gemini returns a response where ALL parts have
thought=True(the model only thought but produced no output):_response_to_create_message_result):response.textreturnsNonebecause Google SDK's_get_text()filters thought parts. The handler crashes withValueError: No content in response: STOP._response_to_result_with_tools): Once Bug 1 is fixed and thought parts are filtered, thecontentlist will be empty, crashing withValueError: No content in response from completion.Bug 3: Safety-filtered responses produce unhelpful errors
When Gemini blocks a response (safety filter, recitation, etc.):
finish_reason:"No content in response: SAFETY"— somewhat helpfulfinish_reason: just"No content in response from completion"— no indication of whyThe tool-path error should include
candidate.finish_reasonso callers can distinguish between "model returned nothing" vs "response was blocked by safety filter".Reproduction
All three bugs are reproducible without API calls:
Version
fastmcp 3.2.3, google-genai 1.72.0