mirror of
https://github.com/aljazceru/mcp-python-sdk.git
synced 2025-12-19 14:54:24 +01:00
feat: add cursor pagination support to all client list methods (#718)
This commit is contained in:
@@ -201,23 +201,29 @@ class ClientSession(
|
|||||||
types.EmptyResult,
|
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."""
|
"""Send a resources/list request."""
|
||||||
return await self.send_request(
|
return await self.send_request(
|
||||||
types.ClientRequest(
|
types.ClientRequest(
|
||||||
types.ListResourcesRequest(
|
types.ListResourcesRequest(
|
||||||
method="resources/list",
|
method="resources/list",
|
||||||
|
cursor=cursor,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
types.ListResourcesResult,
|
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."""
|
"""Send a resources/templates/list request."""
|
||||||
return await self.send_request(
|
return await self.send_request(
|
||||||
types.ClientRequest(
|
types.ClientRequest(
|
||||||
types.ListResourceTemplatesRequest(
|
types.ListResourceTemplatesRequest(
|
||||||
method="resources/templates/list",
|
method="resources/templates/list",
|
||||||
|
cursor=cursor,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
types.ListResourceTemplatesResult,
|
types.ListResourceTemplatesResult,
|
||||||
@@ -278,12 +284,13 @@ class ClientSession(
|
|||||||
request_read_timeout_seconds=read_timeout_seconds,
|
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."""
|
"""Send a prompts/list request."""
|
||||||
return await self.send_request(
|
return await self.send_request(
|
||||||
types.ClientRequest(
|
types.ClientRequest(
|
||||||
types.ListPromptsRequest(
|
types.ListPromptsRequest(
|
||||||
method="prompts/list",
|
method="prompts/list",
|
||||||
|
cursor=cursor,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
types.ListPromptsResult,
|
types.ListPromptsResult,
|
||||||
@@ -322,12 +329,13 @@ class ClientSession(
|
|||||||
types.CompleteResult,
|
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."""
|
"""Send a tools/list request."""
|
||||||
return await self.send_request(
|
return await self.send_request(
|
||||||
types.ClientRequest(
|
types.ClientRequest(
|
||||||
types.ListToolsRequest(
|
types.ListToolsRequest(
|
||||||
method="tools/list",
|
method="tools/list",
|
||||||
|
cursor=cursor,
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
types.ListToolsResult,
|
types.ListToolsResult,
|
||||||
|
|||||||
142
tests/client/test_list_methods_cursor.py
Normal file
142
tests/client/test_list_methods_cursor.py
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user