Skip to content

Commit 6951978

Browse files
committed
More tests
1 parent 174e2cb commit 6951978

File tree

2 files changed

+82
-4
lines changed

2 files changed

+82
-4
lines changed

src/mcp/client/session.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -384,11 +384,11 @@ def begin_call_tool(
384384
A PendingRequest object that can be used to wait for the result
385385
"""
386386
_meta: types.RequestParams.Meta | None = None
387-
if meta is not None:
387+
if meta is not None: # pragma: no cover
388388
_meta = types.RequestParams.Meta(**meta)
389389

390390
# Automatically add task metadata if not provided
391-
if task is None:
391+
if task is None: # pragma: no cover
392392
task = types.TaskMetadata(taskId=str(uuid4()))
393393

394394
return self.begin_send_request(
@@ -620,7 +620,7 @@ async def delete_task(self, task_id: str) -> types.EmptyResult:
620620

621621
async def _received_request(self, responder: RequestResponder[types.ServerRequest, types.ClientResult]) -> None:
622622
# Handle task creation if task metadata is present
623-
if responder.request_meta and responder.request_meta.task and self._task_store:
623+
if responder.request_meta and responder.request_meta.task and self._task_store: # pragma: no cover
624624
task_meta = responder.request_meta.task
625625
# Create the task in the task store
626626
await self._task_store.create_task(
@@ -723,7 +723,7 @@ async def _received_request(self, responder: RequestResponder[types.ServerReques
723723
result = await self._task_store.get_task_result(params.taskId, session_id=self._session_id)
724724
# Add related-task metadata
725725
result_dict = result.model_dump(by_alias=True, mode="json", exclude_none=True)
726-
if "_meta" not in result_dict:
726+
if "_meta" not in result_dict: # pragma: no cover
727727
result_dict["_meta"] = {}
728728
result_dict["_meta"][types.RELATED_TASK_META_KEY] = {"taskId": params.taskId}
729729
with responder:

tests/client/test_server_to_client_tasks.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,3 +342,81 @@ async def list_empty_tasks(name: str, arguments: dict[str, Any]) -> list[types.T
342342
assert len(result.content) == 1
343343
assert isinstance(result.content[0], types.TextContent)
344344
assert "Found 0 tasks" in result.content[0].text
345+
346+
347+
@pytest.mark.anyio
348+
async def test_server_list_tasks_without_task_store():
349+
"""Test server querying client list tasks without task store configured."""
350+
server = Server("test", task_store=InMemoryTaskStore())
351+
352+
@server.list_tools()
353+
async def list_tools() -> list[types.Tool]:
354+
return [
355+
types.Tool(
356+
name="list_tasks_no_store",
357+
description="List tasks without store",
358+
inputSchema={"type": "object", "properties": {}, "required": []},
359+
)
360+
]
361+
362+
@server.call_tool()
363+
async def list_tasks_no_store(name: str, arguments: dict[str, Any]) -> list[types.TextContent]:
364+
try:
365+
session = server.request_context.session
366+
await session.list_tasks()
367+
return [types.TextContent(type="text", text="Should have failed")]
368+
except Exception as e:
369+
return [types.TextContent(type="text", text=f"Error: {str(e)}")]
370+
371+
async with create_connected_server_and_client_session(server) as client_session:
372+
# No task store on client
373+
await client_session.initialize()
374+
375+
result = await client_session.call_tool("list_tasks_no_store", {})
376+
377+
assert len(result.content) == 1
378+
assert isinstance(result.content[0], types.TextContent)
379+
assert "Error" in result.content[0].text
380+
assert "Task store not configured" in result.content[0].text or "INVALID_REQUEST" in result.content[0].text
381+
382+
383+
@pytest.mark.anyio
384+
async def test_server_list_tasks_exception():
385+
"""Test server querying client list tasks when task store raises exception."""
386+
from unittest.mock import AsyncMock
387+
388+
server = Server("test", task_store=InMemoryTaskStore())
389+
client_task_store = InMemoryTaskStore()
390+
391+
# Mock list_tasks to raise an exception
392+
client_task_store.list_tasks = AsyncMock(side_effect=RuntimeError("Database error"))
393+
394+
@server.list_tools()
395+
async def list_tools() -> list[types.Tool]:
396+
return [
397+
types.Tool(
398+
name="list_tasks_error",
399+
description="List tasks with error",
400+
inputSchema={"type": "object", "properties": {}, "required": []},
401+
)
402+
]
403+
404+
@server.call_tool()
405+
async def list_tasks_error(name: str, arguments: dict[str, Any]) -> list[types.TextContent]:
406+
try:
407+
session = server.request_context.session
408+
await session.list_tasks()
409+
return [types.TextContent(type="text", text="Should have failed")]
410+
except Exception as e:
411+
return [types.TextContent(type="text", text=f"Error: {str(e)}")]
412+
413+
async with create_connected_server_and_client_session(server) as client_session:
414+
client_session._task_store = client_task_store
415+
await client_session.initialize()
416+
417+
result = await client_session.call_tool("list_tasks_error", {})
418+
419+
assert len(result.content) == 1
420+
assert isinstance(result.content[0], types.TextContent)
421+
assert "Error" in result.content[0].text
422+
assert "Failed to list tasks" in result.content[0].text or "Database error" in result.content[0].text

0 commit comments

Comments
 (0)