diff --git a/src/mcp/server/mcpserver/utilities/func_metadata.py b/src/mcp/server/mcpserver/utilities/func_metadata.py index 2037b860a1..f63c59198d 100644 --- a/src/mcp/server/mcpserver/utilities/func_metadata.py +++ b/src/mcp/server/mcpserver/utilities/func_metadata.py @@ -590,6 +590,9 @@ def _convert_to_content(result: Any) -> list[ContentBlock]: return [result.to_audio_content()] if isinstance(result, list | tuple): + if not result: + return [TextContent(type="text", text="[]" if isinstance(result, list) else "()")] + return list( chain.from_iterable( _convert_to_content(item) diff --git a/tests/interaction/mcpserver/test_tools.py b/tests/interaction/mcpserver/test_tools.py index a6418ac9c5..769e0fb435 100644 --- a/tests/interaction/mcpserver/test_tools.py +++ b/tests/interaction/mcpserver/test_tools.py @@ -441,3 +441,24 @@ async def collect(message: IncomingMessage) -> None: assert received == snapshot( [LoggingMessageNotification(params=LoggingMessageNotificationParams(level="info", data="tool set changed"))] ) + + +async def test_tool_returning_empty_list_and_tuple_produces_text_content(connect: Connect, unstamped: Unstamp) -> None: + """A tool returning an empty list or tuple produces a TextContent block rather than zero content blocks.""" + mcp = MCPServer("empty-tools") + + @mcp.tool() + def get_empty_list() -> list[str]: + return [] + + @mcp.tool() + def get_empty_tuple() -> tuple[str, ...]: + return () + + async with connect(mcp) as client: + list_result = await client.call_tool("get_empty_list", {}) + tuple_result = await client.call_tool("get_empty_tuple", {}) + + assert list_result.content == [TextContent(text="[]")] + assert tuple_result.content == [TextContent(text="()")] + diff --git a/tests/server/mcpserver/test_func_metadata.py b/tests/server/mcpserver/test_func_metadata.py index 2dfe5d389d..1226a27993 100644 --- a/tests/server/mcpserver/test_func_metadata.py +++ b/tests/server/mcpserver/test_func_metadata.py @@ -1376,3 +1376,23 @@ def fn() -> StepA | StepB: ... # pragma: no branch meta = func_metadata(fn) assert meta.output_schema is None + + +def test_convert_to_content_empty_list_and_tuple(): + from mcp.server.mcpserver.utilities.func_metadata import _convert_to_content + + empty_list_res = _convert_to_content([]) + assert len(empty_list_res) == 1 + assert isinstance(empty_list_res[0], TextContent) + assert empty_list_res[0].text == "[]" + + empty_tuple_res = _convert_to_content(()) + assert len(empty_tuple_res) == 1 + assert isinstance(empty_tuple_res[0], TextContent) + assert empty_tuple_res[0].text == "()" + + non_empty_res = _convert_to_content(["hello", "world"]) + assert len(non_empty_res) == 2 + assert non_empty_res[0].text == "hello" + assert non_empty_res[1].text == "world" +