fix: strip title fields from tool schemas for Gemini 2.5 Flash compatibility (#3861)

* fix: strip title fields from tool schemas for Gemini compatibility

Gemini 2.5 Flash produces MALFORMED_FUNCTION_CALL when a function
declaration's parameters_json_schema contains 'title' fields (which
Pydantic adds by default). Strip them in _convert_tool_to_google_genai
before passing to the API.

Fixes #3860

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* Fix ty errors and lowest-deps test failure

- Add assertions for non-None before subscripting FunctionDeclaration
  fields (ty check)
- Test compress_schema directly instead of constructing FunctionDeclaration
  which may not support parameters_json_schema in google-genai==1.18.0

🤖 Generated with Claude Code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Bill Easton 2026-04-12 11:51:49 -05:00 committed by GitHub
commit 671eaf0f03
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 3 deletions

View file

@ -144,15 +144,19 @@ class GoogleGenaiSamplingHandler:
def _convert_tool_to_google_genai(tool: MCPTool) -> GoogleTool:
"""Convert an MCP Tool to Google GenAI format.
Google's parameters_json_schema accepts standard JSON Schema format,
so we pass tool.inputSchema directly without conversion.
We prune ``title`` fields from the schema because Gemini 2.5 Flash
produces ``MALFORMED_FUNCTION_CALL`` when Pydantic's auto-generated
title annotations are present.
"""
from fastmcp.utilities.json_schema import compress_schema
schema = compress_schema(tool.inputSchema, prune_titles=True)
return GoogleTool(
function_declarations=[
FunctionDeclaration(
name=tool.name,
description=tool.description or "",
parameters_json_schema=tool.inputSchema,
parameters_json_schema=schema,
)
]
)

View file

@ -432,3 +432,31 @@ def test_response_to_result_with_tools_mixed_content():
assert isinstance(tool_use, ToolUseContent)
assert tool_use.type == "tool_use"
assert tool_use.name == "search"
# ────────────────────────────────────────────────────────────
# Test for title stripping (PR #3860)
# ────────────────────────────────────────────────────────────
def test_convert_tool_strips_titles():
"""_convert_tool_to_google_genai should strip titles from inputSchema.
We test via compress_schema directly rather than constructing a
FunctionDeclaration because older google-genai versions may not
support the parameters_json_schema field.
"""
from fastmcp.utilities.json_schema import compress_schema
input_schema = {
"title": "Params",
"type": "object",
"properties": {
"query": {"title": "Query", "type": "string"},
},
}
schema = compress_schema(input_schema, prune_titles=True)
assert "title" not in schema
assert "title" not in schema["properties"]["query"]
assert schema["properties"]["query"]["type"] == "string"