diff --git a/libs/agno/agno/utils/mcp.py b/libs/agno/agno/utils/mcp.py index 16a0c656eb..c222359c39 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 5f5dee555e..e444297602 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()