diff --git a/src/agents/mcp/server.py b/src/agents/mcp/server.py index cdc8927b55..89f74afe48 100644 --- a/src/agents/mcp/server.py +++ b/src/agents/mcp/server.py @@ -1443,8 +1443,8 @@ async def list_tools( transport_cause: Exception | None = None try: tools: list[MCPTool] - # Return from cache if caching is enabled, we have tools, and the cache is not dirty - if self.cache_tools_list and not self._cache_dirty and self._tools_list: + # Return from cache if caching is enabled, the cache is populated, and it is not dirty + if self.cache_tools_list and not self._cache_dirty and self._tools_list is not None: tools = self._tools_list else: tools = [] diff --git a/tests/mcp/test_caching.py b/tests/mcp/test_caching.py index dc30f5d61f..5d6dbc3d49 100644 --- a/tests/mcp/test_caching.py +++ b/tests/mcp/test_caching.py @@ -64,6 +64,33 @@ async def test_server_caching_works( assert result_tools == tools +@pytest.mark.asyncio +@patch("mcp.client.stdio.stdio_client", return_value=DummyStreamsContextManager()) +@patch("mcp.client.session.ClientSession.initialize", new_callable=AsyncMock, return_value=None) +@patch("mcp.client.session.ClientSession.list_tools") +async def test_server_caching_works_with_empty_tools( + mock_list_tools: AsyncMock, mock_initialize: AsyncMock, mock_stdio_client +): + """An empty tools list is a populated cache and must not be fetched again.""" + server = MCPServerStdio( + params={ + "command": tee, + }, + cache_tools_list=True, + ) + mock_list_tools.return_value = ListToolsResult(tools=[]) + + async with server: + first_result = await server.list_tools() + assert first_result == [] + assert server.cached_tools == [] + + second_result = await server.list_tools() + assert second_result == [] + + assert mock_list_tools.call_count == 1 + + @pytest.mark.asyncio @patch("mcp.client.stdio.stdio_client", return_value=DummyStreamsContextManager()) @patch("mcp.client.session.ClientSession.initialize", new_callable=AsyncMock, return_value=None)