From b334d37069fa50e613d98643873b6c43aa0b932b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E6=94=BF=E8=BE=BE?= <1242427577@qq.com> Date: Thu, 2 Jul 2026 05:04:24 +0800 Subject: [PATCH] fix: surface MCP structured content output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: 李政达 <1242427577@qq.com> --- libs/agno/agno/utils/mcp.py | 16 ++++++++++++++++ libs/agno/tests/unit/tools/test_mcp.py | 25 +++++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/libs/agno/agno/utils/mcp.py b/libs/agno/agno/utils/mcp.py index 16a0c656eb3..c222359c390 100644 --- a/libs/agno/agno/utils/mcp.py +++ b/libs/agno/agno/utils/mcp.py @@ -146,6 +146,9 @@ async def _call_with_session(active_session: ClientSession) -> ToolResult: # Handle other content types response_str += f"[Unsupported content type: {content_item.type}]\n" + if not response_str.strip(): + response_str = _structured_content_as_tool_output(result) or "" + return ToolResult( content=response_str.strip(), metadata=_build_mcp_metadata(result), @@ -214,6 +217,19 @@ def _build_mcp_metadata(result: "CallToolResult") -> Optional[Dict[str, Any]]: return metadata or None +def _structured_content_as_tool_output(result: "CallToolResult") -> Optional[str]: + """Serialize structuredContent so structured-only MCP responses reach the model loop.""" + + structured_content = getattr(result, "structuredContent", None) + if structured_content is None: + return None + + try: + return json.dumps(structured_content, ensure_ascii=False, sort_keys=True, default=str) + except (TypeError, ValueError): + return str(structured_content) + + def prepare_command(command: str) -> list[str]: """Sanitize a command and split it into parts before using it to run a MCP server.""" import os diff --git a/libs/agno/tests/unit/tools/test_mcp.py b/libs/agno/tests/unit/tools/test_mcp.py index 5f5dee555e2..e444297602c 100644 --- a/libs/agno/tests/unit/tools/test_mcp.py +++ b/libs/agno/tests/unit/tools/test_mcp.py @@ -1,3 +1,4 @@ +import json from unittest.mock import AsyncMock, MagicMock, patch import pytest @@ -1053,9 +1054,33 @@ async def test_mcp_tool_result_preserves_structured_content(): entrypoint = get_entrypoint_for_tool(mock_tool, session) result = await entrypoint() + assert result.content == "hello" assert result.metadata["structured_content"] == {"id": "u1", "name": "Ada"} +@pytest.mark.asyncio +async def test_mcp_tool_result_uses_structured_content_when_content_is_empty(): + mock_tool = MagicMock() + mock_tool.name = "get_data" + structured_content = {"id": "u1", "name": "Ada", "role": "EMPLOYEE"} + + session = AsyncMock() + session.send_ping = AsyncMock() + session.call_tool = AsyncMock( + return_value=CallToolResult( + content=[], + isError=False, + structuredContent=structured_content, + ) + ) + + entrypoint = get_entrypoint_for_tool(mock_tool, session) + result = await entrypoint() + + assert json.loads(result.content) == structured_content + assert result.metadata["structured_content"] == structured_content + + @pytest.mark.asyncio async def test_mcp_tool_error_result_preserves_structured_content(): mock_tool = MagicMock()