diff --git a/src/mcp/client/session.py b/src/mcp/client/session.py index 1e8ab2042..15e8809c1 100644 --- a/src/mcp/client/session.py +++ b/src/mcp/client/session.py @@ -201,23 +201,29 @@ async def set_logging_level(self, level: types.LoggingLevel) -> types.EmptyResul types.EmptyResult, ) - async def list_resources(self) -> types.ListResourcesResult: + async def list_resources( + self, cursor: str | None = None + ) -> types.ListResourcesResult: """Send a resources/list request.""" return await self.send_request( types.ClientRequest( types.ListResourcesRequest( method="resources/list", + cursor=cursor, ) ), types.ListResourcesResult, ) - async def list_resource_templates(self) -> types.ListResourceTemplatesResult: + async def list_resource_templates( + self, cursor: str | None = None + ) -> types.ListResourceTemplatesResult: """Send a resources/templates/list request.""" return await self.send_request( types.ClientRequest( types.ListResourceTemplatesRequest( method="resources/templates/list", + cursor=cursor, ) ), types.ListResourceTemplatesResult, @@ -278,12 +284,13 @@ async def call_tool( request_read_timeout_seconds=read_timeout_seconds, ) - async def list_prompts(self) -> types.ListPromptsResult: + async def list_prompts(self, cursor: str | None = None) -> types.ListPromptsResult: """Send a prompts/list request.""" return await self.send_request( types.ClientRequest( types.ListPromptsRequest( method="prompts/list", + cursor=cursor, ) ), types.ListPromptsResult, @@ -322,12 +329,13 @@ async def complete( types.CompleteResult, ) - async def list_tools(self) -> types.ListToolsResult: + async def list_tools(self, cursor: str | None = None) -> types.ListToolsResult: """Send a tools/list request.""" return await self.send_request( types.ClientRequest( types.ListToolsRequest( method="tools/list", + cursor=cursor, ) ), types.ListToolsResult, diff --git a/tests/client/test_list_methods_cursor.py b/tests/client/test_list_methods_cursor.py new file mode 100644 index 000000000..f07473f4c --- /dev/null +++ b/tests/client/test_list_methods_cursor.py @@ -0,0 +1,142 @@ +import pytest + +from mcp.server.fastmcp import FastMCP +from mcp.shared.memory import ( + create_connected_server_and_client_session as create_session, +) + +# Mark the whole module for async tests +pytestmark = pytest.mark.anyio + + +async def test_list_tools_cursor_parameter(): + """Test that the cursor parameter is accepted for list_tools. + + Note: FastMCP doesn't currently implement pagination, so this test + only verifies that the cursor parameter is accepted by the client. + """ + server = FastMCP("test") + + # Create a couple of test tools + @server.tool(name="test_tool_1") + async def test_tool_1() -> str: + """First test tool""" + return "Result 1" + + @server.tool(name="test_tool_2") + async def test_tool_2() -> str: + """Second test tool""" + return "Result 2" + + async with create_session(server._mcp_server) as client_session: + # Test without cursor parameter (omitted) + result1 = await client_session.list_tools() + assert len(result1.tools) == 2 + + # Test with cursor=None + result2 = await client_session.list_tools(cursor=None) + assert len(result2.tools) == 2 + + # Test with cursor as string + result3 = await client_session.list_tools(cursor="some_cursor_value") + assert len(result3.tools) == 2 + + # Test with empty string cursor + result4 = await client_session.list_tools(cursor="") + assert len(result4.tools) == 2 + + +async def test_list_resources_cursor_parameter(): + """Test that the cursor parameter is accepted for list_resources. + + Note: FastMCP doesn't currently implement pagination, so this test + only verifies that the cursor parameter is accepted by the client. + """ + server = FastMCP("test") + + # Create a test resource + @server.resource("resource://test/data") + async def test_resource() -> str: + """Test resource""" + return "Test data" + + async with create_session(server._mcp_server) as client_session: + # Test without cursor parameter (omitted) + result1 = await client_session.list_resources() + assert len(result1.resources) >= 1 + + # Test with cursor=None + result2 = await client_session.list_resources(cursor=None) + assert len(result2.resources) >= 1 + + # Test with cursor as string + result3 = await client_session.list_resources(cursor="some_cursor") + assert len(result3.resources) >= 1 + + # Test with empty string cursor + result4 = await client_session.list_resources(cursor="") + assert len(result4.resources) >= 1 + + +async def test_list_prompts_cursor_parameter(): + """Test that the cursor parameter is accepted for list_prompts. + + Note: FastMCP doesn't currently implement pagination, so this test + only verifies that the cursor parameter is accepted by the client. + """ + server = FastMCP("test") + + # Create a test prompt + @server.prompt() + async def test_prompt(name: str) -> str: + """Test prompt""" + return f"Hello, {name}!" + + async with create_session(server._mcp_server) as client_session: + # Test without cursor parameter (omitted) + result1 = await client_session.list_prompts() + assert len(result1.prompts) >= 1 + + # Test with cursor=None + result2 = await client_session.list_prompts(cursor=None) + assert len(result2.prompts) >= 1 + + # Test with cursor as string + result3 = await client_session.list_prompts(cursor="some_cursor") + assert len(result3.prompts) >= 1 + + # Test with empty string cursor + result4 = await client_session.list_prompts(cursor="") + assert len(result4.prompts) >= 1 + + +async def test_list_resource_templates_cursor_parameter(): + """Test that the cursor parameter is accepted for list_resource_templates. + + Note: FastMCP doesn't currently implement pagination, so this test + only verifies that the cursor parameter is accepted by the client. + """ + server = FastMCP("test") + + # Create a test resource template + @server.resource("resource://test/{name}") + async def test_template(name: str) -> str: + """Test resource template""" + return f"Data for {name}" + + async with create_session(server._mcp_server) as client_session: + # Test without cursor parameter (omitted) + result1 = await client_session.list_resource_templates() + assert len(result1.resourceTemplates) >= 1 + + # Test with cursor=None + result2 = await client_session.list_resource_templates(cursor=None) + assert len(result2.resourceTemplates) >= 1 + + # Test with cursor as string + result3 = await client_session.list_resource_templates(cursor="some_cursor") + assert len(result3.resourceTemplates) >= 1 + + # Test with empty string cursor + result4 = await client_session.list_resource_templates(cursor="") + assert len(result4.resourceTemplates) >= 1