Skip to content

Commit d8cec61

Browse files
feat: add cursor pagination support to all list methods
- Add cursor parameter to list_resources(), list_prompts(), and list_resource_templates() - Add tests verifying cursor parameter acceptance for all list methods - Note: Server implementations don't yet support pagination, but client is ready
1 parent 10174b9 commit d8cec61

File tree

2 files changed

+115
-3
lines changed

2 files changed

+115
-3
lines changed

src/mcp/client/session.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,23 +201,29 @@ async def set_logging_level(self, level: types.LoggingLevel) -> types.EmptyResul
201201
types.EmptyResult,
202202
)
203203

204-
async def list_resources(self) -> types.ListResourcesResult:
204+
async def list_resources(
205+
self, cursor: str | None = None
206+
) -> types.ListResourcesResult:
205207
"""Send a resources/list request."""
206208
return await self.send_request(
207209
types.ClientRequest(
208210
types.ListResourcesRequest(
209211
method="resources/list",
212+
cursor=cursor,
210213
)
211214
),
212215
types.ListResourcesResult,
213216
)
214217

215-
async def list_resource_templates(self) -> types.ListResourceTemplatesResult:
218+
async def list_resource_templates(
219+
self, cursor: str | None = None
220+
) -> types.ListResourceTemplatesResult:
216221
"""Send a resources/templates/list request."""
217222
return await self.send_request(
218223
types.ClientRequest(
219224
types.ListResourceTemplatesRequest(
220225
method="resources/templates/list",
226+
cursor=cursor,
221227
)
222228
),
223229
types.ListResourceTemplatesResult,
@@ -278,12 +284,13 @@ async def call_tool(
278284
request_read_timeout_seconds=read_timeout_seconds,
279285
)
280286

281-
async def list_prompts(self) -> types.ListPromptsResult:
287+
async def list_prompts(self, cursor: str | None = None) -> types.ListPromptsResult:
282288
"""Send a prompts/list request."""
283289
return await self.send_request(
284290
types.ClientRequest(
285291
types.ListPromptsRequest(
286292
method="prompts/list",
293+
cursor=cursor,
287294
)
288295
),
289296
types.ListPromptsResult,
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import pytest
2+
3+
from mcp.server.fastmcp import FastMCP
4+
from mcp.shared.memory import (
5+
create_connected_server_and_client_session as create_session,
6+
)
7+
8+
# Mark the whole module for async tests
9+
pytestmark = pytest.mark.anyio
10+
11+
12+
async def test_list_resources_cursor_parameter():
13+
"""Test that the cursor parameter is accepted for list_resources.
14+
15+
Note: FastMCP doesn't currently implement pagination, so this test
16+
only verifies that the cursor parameter is accepted by the client.
17+
"""
18+
server = FastMCP("test")
19+
20+
# Create a test resource
21+
@server.resource("resource://test/data")
22+
async def test_resource() -> str:
23+
"""Test resource"""
24+
return "Test data"
25+
26+
async with create_session(server._mcp_server) as client_session:
27+
# Test without cursor parameter (omitted)
28+
result1 = await client_session.list_resources()
29+
assert len(result1.resources) >= 1
30+
31+
# Test with cursor=None
32+
result2 = await client_session.list_resources(cursor=None)
33+
assert len(result2.resources) >= 1
34+
35+
# Test with cursor as string
36+
result3 = await client_session.list_resources(cursor="some_cursor")
37+
assert len(result3.resources) >= 1
38+
39+
# Test with empty string cursor
40+
result4 = await client_session.list_resources(cursor="")
41+
assert len(result4.resources) >= 1
42+
43+
44+
async def test_list_prompts_cursor_parameter():
45+
"""Test that the cursor parameter is accepted for list_prompts.
46+
47+
Note: FastMCP doesn't currently implement pagination, so this test
48+
only verifies that the cursor parameter is accepted by the client.
49+
"""
50+
server = FastMCP("test")
51+
52+
# Create a test prompt
53+
@server.prompt()
54+
async def test_prompt(name: str) -> str:
55+
"""Test prompt"""
56+
return f"Hello, {name}!"
57+
58+
async with create_session(server._mcp_server) as client_session:
59+
# Test without cursor parameter (omitted)
60+
result1 = await client_session.list_prompts()
61+
assert len(result1.prompts) >= 1
62+
63+
# Test with cursor=None
64+
result2 = await client_session.list_prompts(cursor=None)
65+
assert len(result2.prompts) >= 1
66+
67+
# Test with cursor as string
68+
result3 = await client_session.list_prompts(cursor="some_cursor")
69+
assert len(result3.prompts) >= 1
70+
71+
# Test with empty string cursor
72+
result4 = await client_session.list_prompts(cursor="")
73+
assert len(result4.prompts) >= 1
74+
75+
76+
async def test_list_resource_templates_cursor_parameter():
77+
"""Test that the cursor parameter is accepted for list_resource_templates.
78+
79+
Note: FastMCP doesn't currently implement pagination, so this test
80+
only verifies that the cursor parameter is accepted by the client.
81+
"""
82+
server = FastMCP("test")
83+
84+
# Create a test resource template
85+
@server.resource("resource://test/{name}")
86+
async def test_template(name: str) -> str:
87+
"""Test resource template"""
88+
return f"Data for {name}"
89+
90+
async with create_session(server._mcp_server) as client_session:
91+
# Test without cursor parameter (omitted)
92+
result1 = await client_session.list_resource_templates()
93+
assert len(result1.resourceTemplates) >= 1
94+
95+
# Test with cursor=None
96+
result2 = await client_session.list_resource_templates(cursor=None)
97+
assert len(result2.resourceTemplates) >= 1
98+
99+
# Test with cursor as string
100+
result3 = await client_session.list_resource_templates(cursor="some_cursor")
101+
assert len(result3.resourceTemplates) >= 1
102+
103+
# Test with empty string cursor
104+
result4 = await client_session.list_resource_templates(cursor="")
105+
assert len(result4.resourceTemplates) >= 1

0 commit comments

Comments
 (0)