From 40caeecab1c406ad77e093ef5307d9b3ed876ef6 Mon Sep 17 00:00:00 2001 From: Sureel Bhurat Date: Sun, 23 Nov 2025 09:30:52 -0500 Subject: [PATCH] Fix: Prevent double JSON serialization of MCP tool responses Fixes #3676 MCP tool responses arrive as JSON strings but were being double-serialized by _safe_json_serialize(), creating triple-nested JSON that prevented Claude and GPT from parsing tool results. Example of the bug: '{"content": [{"type": "text", "text": "{\n \"type\"..."}]}' This fix adds an isinstance(str) check before serialization. If the response is already a string (from MCP or other sources), it's used directly. Otherwise, it's serialized normally. Impact: Without this fix, agents using LiteLLM with MCP tools would successfully call tools but fail to present results to users, appearing to hang or produce incomplete responses. Tested with Claude Sonnet 4.5 and GPT-5 via Azure OpenAI with MCP tools (Google Drive, HubSpot CRM) in production multi-agent system. --- src/google/adk/models/lite_llm.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/google/adk/models/lite_llm.py b/src/google/adk/models/lite_llm.py index 9e3698b190..f47485e3c2 100644 --- a/src/google/adk/models/lite_llm.py +++ b/src/google/adk/models/lite_llm.py @@ -367,11 +367,21 @@ def _content_to_message_param( tool_messages = [] for part in content.parts: if part.function_response: + # FIX: Check if response is already a string before serializing. + # MCP tool responses come as JSON strings, but _safe_json_serialize was + # double-serializing them (json.dumps on already-JSON strings), causing + # triple-nested JSON like: '{"content": [{"type": "text", "text": "{\n \"type\"..."}]}' + # This prevented Claude/GPT from parsing tool results correctly. + response_content = ( + part.function_response.response + if isinstance(part.function_response.response, str) + else _safe_json_serialize(part.function_response.response) + ) tool_messages.append( ChatCompletionToolMessage( role="tool", tool_call_id=part.function_response.id, - content=_safe_json_serialize(part.function_response.response), + content=response_content, ) ) if tool_messages: