Skip to content

Commit 8280c7c

Browse files
committed
Fixes coverage issues for nested functions inside test functions
1 parent f6d18c4 commit 8280c7c

File tree

2 files changed

+109
-15
lines changed

2 files changed

+109
-15
lines changed

src/mcp/client/session.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -52,32 +52,32 @@ class ProgressNotificationFnT(Protocol):
5252
async def __call__(
5353
self,
5454
params: types.ProgressNotificationParams,
55-
) -> None: ...
55+
) -> None: ... # pragma: no branch
5656

5757

5858
class ResourceUpdatedFnT(Protocol):
5959
async def __call__(
6060
self,
6161
params: types.ResourceUpdatedNotificationParams,
62-
) -> None: ...
62+
) -> None: ... # pragma: no branch
6363

6464

6565
class ResourceListChangedFnT(Protocol):
6666
async def __call__(
6767
self,
68-
) -> None: ...
68+
) -> None: ... # pragma: no branch
6969

7070

7171
class ToolListChangedFnT(Protocol):
7272
async def __call__(
7373
self,
74-
) -> None: ...
74+
) -> None: ... # pragma: no branch
7575

7676

7777
class PromptListChangedFnT(Protocol):
7878
async def __call__(
7979
self,
80-
) -> None: ...
80+
) -> None: ... # pragma: no branch
8181

8282

8383
class MessageHandlerFnT(Protocol):
@@ -628,7 +628,7 @@ async def _received_notification(self, notification: types.ServerNotification) -
628628
await self._tool_list_changed_callback()
629629
case types.PromptListChangedNotification():
630630
await self._prompt_list_changed_callback()
631-
case _:
631+
case _: # pragma: no cover
632632
# CancelledNotification is handled separately in shared/session.py
633633
# and should never reach this point. This case is defensive.
634634
pass

tests/client/test_notification_callbacks.py

Lines changed: 103 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ async def send_progress_tool(progress: float, total: float, message: str) -> boo
124124
"""Send a progress notification to the client."""
125125
# Get the progress token from the request metadata
126126
ctx = server.get_context()
127-
if ctx.request_context.meta and ctx.request_context.meta.progressToken:
127+
if ctx.request_context.meta and ctx.request_context.meta.progressToken: # pragma: no branch
128128
await ctx.session.send_progress_notification(
129129
progress_token=ctx.request_context.meta.progressToken,
130130
progress=progress,
@@ -137,7 +137,7 @@ async def message_handler(
137137
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
138138
) -> None:
139139
"""Handle exceptions from the session."""
140-
if isinstance(message, Exception):
140+
if isinstance(message, Exception): # pragma: no cover
141141
raise message
142142

143143
async with create_session(
@@ -179,7 +179,7 @@ async def message_handler(
179179
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
180180
) -> None:
181181
"""Handle exceptions from the session."""
182-
if isinstance(message, Exception):
182+
if isinstance(message, Exception): # pragma: no cover
183183
raise message
184184

185185
async with create_session(
@@ -216,7 +216,7 @@ async def message_handler(
216216
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
217217
) -> None:
218218
"""Handle exceptions from the session."""
219-
if isinstance(message, Exception):
219+
if isinstance(message, Exception): # pragma: no cover
220220
raise message
221221

222222
async with create_session(
@@ -249,7 +249,7 @@ async def message_handler(
249249
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
250250
) -> None:
251251
"""Handle exceptions from the session."""
252-
if isinstance(message, Exception):
252+
if isinstance(message, Exception): # pragma: no cover
253253
raise message
254254

255255
async with create_session(
@@ -282,7 +282,7 @@ async def message_handler(
282282
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
283283
) -> None:
284284
"""Handle exceptions from the session."""
285-
if isinstance(message, Exception):
285+
if isinstance(message, Exception): # pragma: no cover
286286
raise message
287287

288288
async with create_session(
@@ -374,7 +374,7 @@ async def test_notification_callback_parametrized(
374374
async def send_progress_tool(progress: float, total: float, message: str) -> bool:
375375
"""Send a progress notification to the client."""
376376
ctx = server.get_context()
377-
if ctx.request_context.meta and ctx.request_context.meta.progressToken:
377+
if ctx.request_context.meta and ctx.request_context.meta.progressToken: # pragma: no branch
378378
await ctx.session.send_progress_notification(
379379
progress_token=ctx.request_context.meta.progressToken,
380380
progress=progress,
@@ -411,7 +411,7 @@ async def message_handler(
411411
message: RequestResponder[types.ServerRequest, types.ClientResult] | types.ServerNotification | Exception,
412412
) -> None:
413413
"""Handle exceptions from the session."""
414-
if isinstance(message, Exception):
414+
if isinstance(message, Exception): # pragma: no cover
415415
raise message
416416

417417
# Create session with the appropriate callback
@@ -444,7 +444,7 @@ async def test_all_default_callbacks_with_notifications() -> None:
444444
async def send_progress_tool(progress: float, total: float) -> bool:
445445
"""Send a progress notification."""
446446
ctx = server.get_context()
447-
if ctx.request_context.meta and ctx.request_context.meta.progressToken:
447+
if ctx.request_context.meta and ctx.request_context.meta.progressToken: # pragma: no branch
448448
await ctx.session.send_progress_notification(
449449
progress_token=ctx.request_context.meta.progressToken,
450450
progress=progress,
@@ -506,3 +506,97 @@ async def send_prompt_list_changed_tool() -> bool:
506506
# Test prompt list changed with default callback
507507
result5 = await client_session.call_tool("send_prompt_list_changed", {})
508508
assert result5.isError is False
509+
510+
511+
@pytest.mark.anyio
512+
async def test_progress_tool_without_progress_token() -> None:
513+
"""Test progress tool when no progress token is provided in metadata."""
514+
from mcp.server.fastmcp import FastMCP
515+
516+
progress_collector = ProgressNotificationCollector()
517+
server = FastMCP("test")
518+
519+
@server.tool("send_progress")
520+
async def send_progress_tool(progress: float, total: float, message: str) -> bool:
521+
"""Send a progress notification to the client."""
522+
ctx = server.get_context()
523+
# This branch: when meta is None or no progressToken
524+
if ctx.request_context.meta and ctx.request_context.meta.progressToken: # pragma: no cover
525+
await ctx.session.send_progress_notification(
526+
progress_token=ctx.request_context.meta.progressToken,
527+
progress=progress,
528+
total=total,
529+
message=message,
530+
)
531+
return True
532+
533+
async with create_session(server._mcp_server, progress_notification_callback=progress_collector) as client_session:
534+
# Call without meta - takes the False branch
535+
result = await client_session.call_tool(
536+
"send_progress",
537+
{"progress": 50.0, "total": 100.0, "message": "test"},
538+
)
539+
assert result.isError is False
540+
# No notification should be sent when no progress token is provided
541+
assert len(progress_collector.notifications) == 0
542+
543+
544+
@pytest.mark.anyio
545+
async def test_parametrized_progress_tool_without_progress_token() -> None:
546+
"""Test parametrized progress tool when no progress token is provided."""
547+
from mcp.server.fastmcp import FastMCP
548+
549+
progress_collector = ProgressNotificationCollector()
550+
server = FastMCP("test")
551+
552+
@server.tool("send_progress")
553+
async def send_progress_tool(progress: float, total: float, message: str) -> bool:
554+
"""Send a progress notification to the client."""
555+
ctx = server.get_context()
556+
if ctx.request_context.meta and ctx.request_context.meta.progressToken: # pragma: no cover
557+
await ctx.session.send_progress_notification(
558+
progress_token=ctx.request_context.meta.progressToken,
559+
progress=progress,
560+
total=total,
561+
message=message,
562+
)
563+
return True
564+
565+
async with create_session(server._mcp_server, progress_notification_callback=progress_collector) as client_session:
566+
# Call with empty meta dict - takes the False branch
567+
result = await client_session.call_tool(
568+
"send_progress",
569+
{"progress": 75.0, "total": 100.0, "message": "Almost done"},
570+
meta={},
571+
)
572+
assert result.isError is False
573+
# No notification should be sent when meta exists but has no progressToken
574+
assert len(progress_collector.notifications) == 0
575+
576+
577+
@pytest.mark.anyio
578+
async def test_default_callback_progress_tool_without_progress_token() -> None:
579+
"""Test that default progress callback handles missing progress token correctly."""
580+
from mcp.server.fastmcp import FastMCP
581+
582+
server = FastMCP("test-server")
583+
584+
@server.tool("send_progress")
585+
async def send_progress_tool(progress: float, total: float) -> bool:
586+
"""Send a progress notification."""
587+
ctx = server.get_context()
588+
if ctx.request_context.meta and ctx.request_context.meta.progressToken: # pragma: no cover
589+
await ctx.session.send_progress_notification(
590+
progress_token=ctx.request_context.meta.progressToken,
591+
progress=progress,
592+
total=total,
593+
)
594+
return True
595+
596+
async with create_session(server._mcp_server) as client_session:
597+
# Call without meta - the False branch is taken in the tool
598+
result = await client_session.call_tool(
599+
"send_progress",
600+
{"progress": 50.0, "total": 100.0},
601+
)
602+
assert result.isError is False

0 commit comments

Comments
 (0)