Skip to content

Commit b779c12

Browse files
test: add params parameter tests and precedence verification
Add comprehensive tests for the new params parameter across all four list methods (list_tools, list_resources, list_prompts, list_resource_templates). Tests verify: 1. params parameter works correctly when omitted, set to None, or set to empty 2. params parameter correctly passes cursor values 3. params parameter takes precedence over deprecated cursor parameter when both are provided, ensuring a safe migration path for existing code These tests ensure the backwards compatibility guarantees are preserved and prevent regressions in precedence behavior.
1 parent 7db0e25 commit b779c12

File tree

1 file changed

+331
-0
lines changed

1 file changed

+331
-0
lines changed

tests/client/test_list_methods_cursor.py

Lines changed: 331 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,337 @@ async def test_template(name: str) -> str:
221221
assert list_templates_requests[0].params["cursor"] == ""
222222

223223

224+
async def test_list_tools_params_parameter(stream_spy: Callable[[], StreamSpyCollection]):
225+
"""Test that the params parameter works correctly for list_tools.
226+
227+
This tests the new params parameter API (non-deprecated) to ensure
228+
it correctly handles all parameter combinations.
229+
"""
230+
server = FastMCP("test")
231+
232+
# Create a couple of test tools
233+
@server.tool(name="test_tool_1")
234+
async def test_tool_1() -> str:
235+
"""First test tool"""
236+
return "Result 1"
237+
238+
@server.tool(name="test_tool_2")
239+
async def test_tool_2() -> str:
240+
"""Second test tool"""
241+
return "Result 2"
242+
243+
async with create_session(server._mcp_server) as client_session:
244+
spies = stream_spy()
245+
246+
# Test without params parameter (omitted)
247+
_ = await client_session.list_tools()
248+
list_tools_requests = spies.get_client_requests(method="tools/list")
249+
assert len(list_tools_requests) == 1
250+
assert list_tools_requests[0].params is None
251+
252+
spies.clear()
253+
254+
# Test with params=None
255+
_ = await client_session.list_tools(params=None)
256+
list_tools_requests = spies.get_client_requests(method="tools/list")
257+
assert len(list_tools_requests) == 1
258+
assert list_tools_requests[0].params is None
259+
260+
spies.clear()
261+
262+
# Test with empty params (for strict servers)
263+
_ = await client_session.list_tools(params=types.PaginatedRequestParams())
264+
list_tools_requests = spies.get_client_requests(method="tools/list")
265+
assert len(list_tools_requests) == 1
266+
assert list_tools_requests[0].params is not None
267+
assert list_tools_requests[0].params.get("cursor") is None
268+
269+
spies.clear()
270+
271+
# Test with params containing cursor
272+
_ = await client_session.list_tools(params=types.PaginatedRequestParams(cursor="some_cursor_value"))
273+
list_tools_requests = spies.get_client_requests(method="tools/list")
274+
assert len(list_tools_requests) == 1
275+
assert list_tools_requests[0].params is not None
276+
assert list_tools_requests[0].params["cursor"] == "some_cursor_value"
277+
278+
279+
async def test_list_resources_params_parameter(stream_spy: Callable[[], StreamSpyCollection]):
280+
"""Test that the params parameter works correctly for list_resources.
281+
282+
This tests the new params parameter API (non-deprecated) to ensure
283+
it correctly handles all parameter combinations.
284+
"""
285+
server = FastMCP("test")
286+
287+
# Create a test resource
288+
@server.resource("resource://test/data")
289+
async def test_resource() -> str:
290+
"""Test resource"""
291+
return "Test data"
292+
293+
async with create_session(server._mcp_server) as client_session:
294+
spies = stream_spy()
295+
296+
# Test without params parameter (omitted)
297+
_ = await client_session.list_resources()
298+
list_resources_requests = spies.get_client_requests(method="resources/list")
299+
assert len(list_resources_requests) == 1
300+
assert list_resources_requests[0].params is None
301+
302+
spies.clear()
303+
304+
# Test with params=None
305+
_ = await client_session.list_resources(params=None)
306+
list_resources_requests = spies.get_client_requests(method="resources/list")
307+
assert len(list_resources_requests) == 1
308+
assert list_resources_requests[0].params is None
309+
310+
spies.clear()
311+
312+
# Test with empty params (for strict servers)
313+
_ = await client_session.list_resources(params=types.PaginatedRequestParams())
314+
list_resources_requests = spies.get_client_requests(method="resources/list")
315+
assert len(list_resources_requests) == 1
316+
assert list_resources_requests[0].params is not None
317+
assert list_resources_requests[0].params.get("cursor") is None
318+
319+
spies.clear()
320+
321+
# Test with params containing cursor
322+
_ = await client_session.list_resources(params=types.PaginatedRequestParams(cursor="some_cursor"))
323+
list_resources_requests = spies.get_client_requests(method="resources/list")
324+
assert len(list_resources_requests) == 1
325+
assert list_resources_requests[0].params is not None
326+
assert list_resources_requests[0].params["cursor"] == "some_cursor"
327+
328+
329+
async def test_list_prompts_params_parameter(stream_spy: Callable[[], StreamSpyCollection]):
330+
"""Test that the params parameter works correctly for list_prompts.
331+
332+
This tests the new params parameter API (non-deprecated) to ensure
333+
it correctly handles all parameter combinations.
334+
"""
335+
server = FastMCP("test")
336+
337+
# Create a test prompt
338+
@server.prompt()
339+
async def test_prompt(name: str) -> str:
340+
"""Test prompt"""
341+
return f"Hello, {name}!"
342+
343+
async with create_session(server._mcp_server) as client_session:
344+
spies = stream_spy()
345+
346+
# Test without params parameter (omitted)
347+
_ = await client_session.list_prompts()
348+
list_prompts_requests = spies.get_client_requests(method="prompts/list")
349+
assert len(list_prompts_requests) == 1
350+
assert list_prompts_requests[0].params is None
351+
352+
spies.clear()
353+
354+
# Test with params=None
355+
_ = await client_session.list_prompts(params=None)
356+
list_prompts_requests = spies.get_client_requests(method="prompts/list")
357+
assert len(list_prompts_requests) == 1
358+
assert list_prompts_requests[0].params is None
359+
360+
spies.clear()
361+
362+
# Test with empty params (for strict servers)
363+
_ = await client_session.list_prompts(params=types.PaginatedRequestParams())
364+
list_prompts_requests = spies.get_client_requests(method="prompts/list")
365+
assert len(list_prompts_requests) == 1
366+
assert list_prompts_requests[0].params is not None
367+
assert list_prompts_requests[0].params.get("cursor") is None
368+
369+
spies.clear()
370+
371+
# Test with params containing cursor
372+
_ = await client_session.list_prompts(params=types.PaginatedRequestParams(cursor="some_cursor"))
373+
list_prompts_requests = spies.get_client_requests(method="prompts/list")
374+
assert len(list_prompts_requests) == 1
375+
assert list_prompts_requests[0].params is not None
376+
assert list_prompts_requests[0].params["cursor"] == "some_cursor"
377+
378+
379+
async def test_list_resource_templates_params_parameter(stream_spy: Callable[[], StreamSpyCollection]):
380+
"""Test that the params parameter works correctly for list_resource_templates.
381+
382+
This tests the new params parameter API (non-deprecated) to ensure
383+
it correctly handles all parameter combinations.
384+
"""
385+
server = FastMCP("test")
386+
387+
# Create a test resource template
388+
@server.resource("resource://test/{name}")
389+
async def test_template(name: str) -> str:
390+
"""Test resource template"""
391+
return f"Data for {name}"
392+
393+
async with create_session(server._mcp_server) as client_session:
394+
spies = stream_spy()
395+
396+
# Test without params parameter (omitted)
397+
_ = await client_session.list_resource_templates()
398+
list_templates_requests = spies.get_client_requests(method="resources/templates/list")
399+
assert len(list_templates_requests) == 1
400+
assert list_templates_requests[0].params is None
401+
402+
spies.clear()
403+
404+
# Test with params=None
405+
_ = await client_session.list_resource_templates(params=None)
406+
list_templates_requests = spies.get_client_requests(method="resources/templates/list")
407+
assert len(list_templates_requests) == 1
408+
assert list_templates_requests[0].params is None
409+
410+
spies.clear()
411+
412+
# Test with empty params (for strict servers)
413+
_ = await client_session.list_resource_templates(params=types.PaginatedRequestParams())
414+
list_templates_requests = spies.get_client_requests(method="resources/templates/list")
415+
assert len(list_templates_requests) == 1
416+
assert list_templates_requests[0].params is not None
417+
assert list_templates_requests[0].params.get("cursor") is None
418+
419+
spies.clear()
420+
421+
# Test with params containing cursor
422+
_ = await client_session.list_resource_templates(params=types.PaginatedRequestParams(cursor="some_cursor"))
423+
list_templates_requests = spies.get_client_requests(method="resources/templates/list")
424+
assert len(list_templates_requests) == 1
425+
assert list_templates_requests[0].params is not None
426+
assert list_templates_requests[0].params["cursor"] == "some_cursor"
427+
428+
429+
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
430+
async def test_list_tools_params_takes_precedence_over_cursor(
431+
stream_spy: Callable[[], StreamSpyCollection],
432+
):
433+
"""Test that params parameter takes precedence over cursor parameter.
434+
435+
When both cursor and params are provided, params should be used and
436+
cursor should be ignored, ensuring safe migration path.
437+
"""
438+
server = FastMCP("test")
439+
440+
@server.tool(name="test_tool")
441+
async def test_tool() -> str:
442+
"""Test tool"""
443+
return "Result"
444+
445+
async with create_session(server._mcp_server) as client_session:
446+
spies = stream_spy()
447+
448+
# Call with both cursor and params - params should take precedence
449+
_ = await client_session.list_tools(
450+
cursor="old_cursor",
451+
params=types.PaginatedRequestParams(cursor="new_cursor"),
452+
)
453+
list_tools_requests = spies.get_client_requests(method="tools/list")
454+
assert len(list_tools_requests) == 1
455+
# Verify params takes precedence (new_cursor should be used, not old_cursor)
456+
assert list_tools_requests[0].params is not None
457+
assert list_tools_requests[0].params["cursor"] == "new_cursor"
458+
459+
460+
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
461+
async def test_list_resources_params_takes_precedence_over_cursor(
462+
stream_spy: Callable[[], StreamSpyCollection],
463+
):
464+
"""Test that params parameter takes precedence over cursor parameter.
465+
466+
When both cursor and params are provided, params should be used and
467+
cursor should be ignored, ensuring safe migration path.
468+
"""
469+
server = FastMCP("test")
470+
471+
@server.resource("resource://test/data")
472+
async def test_resource() -> str:
473+
"""Test resource"""
474+
return "Test data"
475+
476+
async with create_session(server._mcp_server) as client_session:
477+
spies = stream_spy()
478+
479+
# Call with both cursor and params - params should take precedence
480+
_ = await client_session.list_resources(
481+
cursor="old_cursor",
482+
params=types.PaginatedRequestParams(cursor="new_cursor"),
483+
)
484+
list_resources_requests = spies.get_client_requests(method="resources/list")
485+
assert len(list_resources_requests) == 1
486+
# Verify params takes precedence (new_cursor should be used, not old_cursor)
487+
assert list_resources_requests[0].params is not None
488+
assert list_resources_requests[0].params["cursor"] == "new_cursor"
489+
490+
491+
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
492+
async def test_list_prompts_params_takes_precedence_over_cursor(
493+
stream_spy: Callable[[], StreamSpyCollection],
494+
):
495+
"""Test that params parameter takes precedence over cursor parameter.
496+
497+
When both cursor and params are provided, params should be used and
498+
cursor should be ignored, ensuring safe migration path.
499+
"""
500+
server = FastMCP("test")
501+
502+
@server.prompt()
503+
async def test_prompt(name: str) -> str:
504+
"""Test prompt"""
505+
return f"Hello, {name}!"
506+
507+
async with create_session(server._mcp_server) as client_session:
508+
spies = stream_spy()
509+
510+
# Call with both cursor and params - params should take precedence
511+
_ = await client_session.list_prompts(
512+
cursor="old_cursor",
513+
params=types.PaginatedRequestParams(cursor="new_cursor"),
514+
)
515+
list_prompts_requests = spies.get_client_requests(method="prompts/list")
516+
assert len(list_prompts_requests) == 1
517+
# Verify params takes precedence (new_cursor should be used, not old_cursor)
518+
assert list_prompts_requests[0].params is not None
519+
assert list_prompts_requests[0].params["cursor"] == "new_cursor"
520+
521+
522+
@pytest.mark.filterwarnings("ignore::DeprecationWarning")
523+
async def test_list_resource_templates_params_takes_precedence_over_cursor(
524+
stream_spy: Callable[[], StreamSpyCollection],
525+
):
526+
"""Test that params parameter takes precedence over cursor parameter.
527+
528+
When both cursor and params are provided, params should be used and
529+
cursor should be ignored, ensuring safe migration path.
530+
"""
531+
server = FastMCP("test")
532+
533+
@server.resource("resource://test/{name}")
534+
async def test_template(name: str) -> str:
535+
"""Test resource template"""
536+
return f"Data for {name}"
537+
538+
async with create_session(server._mcp_server) as client_session:
539+
spies = stream_spy()
540+
541+
# Call with both cursor and params - params should take precedence
542+
_ = await client_session.list_resource_templates(
543+
cursor="old_cursor",
544+
params=types.PaginatedRequestParams(cursor="new_cursor"),
545+
)
546+
list_templates_requests = spies.get_client_requests(
547+
method="resources/templates/list"
548+
)
549+
assert len(list_templates_requests) == 1
550+
# Verify params takes precedence (new_cursor should be used, not old_cursor)
551+
assert list_templates_requests[0].params is not None
552+
assert list_templates_requests[0].params["cursor"] == "new_cursor"
553+
554+
224555
async def test_list_tools_with_strict_server_validation():
225556
"""Test that list_tools works with strict servers require a params field,
226557
even if it is empty.

0 commit comments

Comments
 (0)