content: from collections.abc import Callable
  
  import pytest
  
  import mcp.types as types
  from mcp.server import Server
  from mcp.server.fastmcp import FastMCP
  from mcp.shared.memory import create_connected_server_and_client_session as create_session
  from mcp.types import ListToolsRequest, ListToolsResult
  
  from .conftest import StreamSpyCollection
  
  pytestmark = pytest.mark.anyio
  
  
  @pytest.fixture
  async def full_featured_server():
      """Create a server with tools, resources, prompts, and templates."""
      server = FastMCP("test")
  
      @server.tool(name="test_tool_1")
      async def test_tool_1() -> str:  # pragma: no cover
          """First test tool"""
          return "Result 1"
  
      @server.tool(name="test_tool_2")
      async def test_tool_2() -> str:  # pragma: no cover
          """Second test tool"""
          return "Result 2"
  
      @server.resource("resource://test/data")
      async def test_resource() -> str:  # pragma: no cover
          """Test resource"""
          return "Test data"
  
      @server.prompt()
      async def test_prompt(name: str) -> str:  # pragma: no cover
          """Test prompt"""
          return f"Hello, {name}!"
  
      @server.resource("resource://test/{name}")
      async def test_template(name: str) -> str:  # pragma: no cover
          """Test resource template"""
          return f"Data for {name}"
  
      return server
  
  
  @pytest.mark.parametrize(
      "method_name,request_method",
      [
          ("list_tools", "tools/list"),
          ("list_resources", "resources/list"),
          ("list_prompts", "prompts/list"),
          ("list_resource_templates", "resources/templates/list"),
      ],
  )
  @pytest.mark.filterwarnings("ignore::DeprecationWarning")
  async def test_list_methods_cursor_parameter(
      stream_spy: Callable[[], StreamSpyCollection],
      full_featured_server: FastMCP,
      method_name: str,
      request_method: str,
  ):
      """Test that the cursor parameter is accepted and correctly passed to the server.
  
      Covers: list_tools, list_resources, list_prompts, list_resource_templates
  
      See: https://modelcontextprotocol.io/specification/2025-03-26/server/utilities/pagination#request-format
      """
      async with create_session(full_featured_server._mcp_server) as client_session:
          spies = stream_spy()
  
          # Test without cursor parameter (omitted)
          method = getattr(client_session, method_name)
          _ = await method()
          requests = spies.get_client_requests(method=request_method)
          assert len(requests) == 1
          assert requests[0].params is None
  
          spies.clear()
  
          # Test with cursor=None
          _ = await method(cursor=None)
          requests = spies.get_client_requests(method=request_method)
          assert len(requests) == 1
          assert requests[0].params is None
  
          spies.clear()
  
          # Test with cursor as string
          _ = await method(cursor="some_cursor_value")
          requests = spies.get_client_requests(method=request_method)
          assert len(requests) == 1
          assert requests[0].params is not None
          assert requests[0].params["cursor"] == "some_cursor_value"
  
          spies.clear()
  
          # Test with empty string cursor
          _ = await method(cursor="")
          requests = spies.get_client_requests(method=request_method)
          assert len(requests) == 1
          assert requests[0].params is not None
          assert requests[0].params["cursor"] == ""
  
  
  @pytest.mark.parametrize(
      "method_name,request_method",
      [
          ("list_tools", "tools/list"),
          ("list_resources", "resources/list"),
          ("list_prompts", "prompts/list"),
          ("list_resource_templates", "resources/templates/list"),
      ],
  )
  async def test_list_methods_params_parameter(
      stream_spy: Callable[[], StreamSpyCollection],
      full_featured_server: FastMCP,
      method_name: str,
      request_method: str,
  ):
      """Test that the params parameter works correctly for list methods.
  
      Covers: list_tools, list_resources, list_prompts, list_resource_templates
  
      This tests the new params parameter API (non-deprecated) to ensure
      it correctly handles all parameter combinations.
      """
      async with create_session(full_featured_server._mcp_server) as client_session:
          spies = stream_spy()
          method = getattr(client_session, method_name)
  
          # Test without params parameter (omitted)
          _ = await method()
          requests = spies.get_client_requests(method=request_method)
          assert len(requests) == 1
          assert requests[0].params is None
  
          spies.clear()
  
          # Test with params=None
          _ = await method(params=None)
          requests = spies.get_client_requests(method=request_method)
          assert len(requests) == 1
          assert requests[0].params is None
  
          spies.clear()
  
          # Test with empty params (for strict servers)
          _ = await method(params=types.PaginatedRequestParams())
          requests = spies.get_client_requests(method=request_method)
          assert len(requests) == 1
          assert requests[0].params is not None
          assert requests[0].params.get("cursor") is None
  
          spies.clear()
  
          # Test with params containing cursor
          _ = await method(params=types.PaginatedRequestParams(cursor="some_cursor_value"))
          requests = spies.get_client_requests(method=request_method)
          assert len(requests) == 1
          assert requests[0].params is not None
          assert requests[0].params["cursor"] == "some_cursor_value"
  
  
  @pytest.mark.parametrize(
      "method_name",
      [
          "list_tools",
          "list_resources",
          "list_prompts",
          "list_resource_templates",
      ],
  )
  async def test_list_methods_raises_error_when_both_cursor_and_params_provided(
      full_featured_server: FastMCP,
      method_name: str,
  ):
      """Test that providing both cursor and params raises ValueError.
  
      Covers: list_tools, list_resources, list_prompts, list_resource_templates
  
      When both cursor and params are provided, a ValueError should be raised
      to prevent ambiguity.
      """
      async with create_session(full_featured_server._mcp_server) as client_session:
          method = getattr(client_session, method_name)
  
          # Call with both cursor and params - should raise ValueError
          with pytest.raises(ValueError, match="Cannot specify both cursor and params"):
              await method(
                  cursor="old_cursor",
                  params=types.PaginatedRequestParams(cursor="new_cursor"),
              )
  
  
  async def test_list_tools_with_strict_server_validation():
      """Test that list_tools works with strict servers require a params field,
      even if it is empty.
  
      Some MCP servers may implement strict JSON-RPC validation that requires
      the params field to always be present in requests, even if empty {}.
  
      This test ensures such servers are supported by the client SDK for list_resources
      requests without a cursor.
      """
  
      server = Server("strict_server")
  
      @server.list_tools()
      async def handle_list_tools(request: ListToolsRequest) -> ListToolsResult:  # pragma: no cover
          """Strict handler that validates params field exists"""
  
          # Simulate strict server validation
          if request.params is None:
              raise ValueError(
                  "Strict server validation failed: params field must be present. "
                  "Expected params: {} for requests without cursor."
              )
  
          # Return empty tools list
          return ListToolsResult(tools=[])
  
      async with create_session(server) as client_session:
          # Use params to explicitly send params: {} for strict server compatibility
          result = await client_session.list_tools(params=types.PaginatedRequestParams())
          assert result is not None
  