Skip to content

Commit 7a3fb21

Browse files
committed
fix: convert all pragma no cover to lax no cover in source files
Server code running in-thread has non-deterministic coverage. Use lax annotation which is excluded by coverage.py but not checked by strict-no-cover, avoiding flaky CI failures.
1 parent 7942963 commit 7a3fb21

File tree

3 files changed

+52
-52
lines changed

3 files changed

+52
-52
lines changed

src/mcp/server/session.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ async def _received_notification(self, notification: types.ClientNotification) -
200200
case types.InitializedNotification():
201201
self._initialization_state = InitializationState.Initialized
202202
case _:
203-
if self._initialization_state != InitializationState.Initialized: # pragma: no cover
203+
if self._initialization_state != InitializationState.Initialized: # pragma: lax no cover
204204
raise RuntimeError("Received notification before initialization was complete")
205205

206206
async def send_log_message(
@@ -446,7 +446,7 @@ async def elicit_url(
446446
metadata=ServerMessageMetadata(related_request_id=related_request_id),
447447
)
448448

449-
async def send_ping(self) -> types.EmptyResult: # pragma: no cover
449+
async def send_ping(self) -> types.EmptyResult: # pragma: lax no cover
450450
"""Send a ping request."""
451451
return await self.send_request(
452452
types.PingRequest(),
@@ -478,11 +478,11 @@ async def send_resource_list_changed(self) -> None:
478478
"""Send a resource list changed notification."""
479479
await self.send_notification(types.ResourceListChangedNotification())
480480

481-
async def send_tool_list_changed(self) -> None: # pragma: no cover
481+
async def send_tool_list_changed(self) -> None: # pragma: lax no cover
482482
"""Send a tool list changed notification."""
483483
await self.send_notification(types.ToolListChangedNotification())
484484

485-
async def send_prompt_list_changed(self) -> None: # pragma: no cover
485+
async def send_prompt_list_changed(self) -> None: # pragma: lax no cover
486486
"""Send a prompt list changed notification."""
487487
await self.send_notification(types.PromptListChangedNotification())
488488

src/mcp/server/streamable_http.py

Lines changed: 32 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ async def store_event(self, stream_id: StreamId, message: JSONRPCMessage | None)
9191
Returns:
9292
The generated event ID for the stored event.
9393
"""
94-
pass # pragma: no cover
94+
pass # pragma: lax no cover
9595

9696
@abstractmethod
9797
async def replay_events_after(
@@ -108,7 +108,7 @@ async def replay_events_after(
108108
Returns:
109109
The stream ID of the replayed events, or None if no events were found.
110110
"""
111-
pass # pragma: no cover
111+
pass # pragma: lax no cover
112112

113113

114114
class StreamableHTTPServerTransport:
@@ -354,7 +354,7 @@ async def _clean_up_memory_streams(self, request_id: RequestId) -> None:
354354
# Close the request stream
355355
await self._request_streams[request_id][0].aclose()
356356
await self._request_streams[request_id][1].aclose()
357-
except Exception: # pragma: no cover
357+
except Exception: # pragma: lax no cover
358358
# During cleanup, we catch all exceptions since streams might be in various states
359359
logger.debug("Error closing memory streams - may already be closed")
360360
finally:
@@ -439,15 +439,15 @@ async def _validate_accept_header(self, request: Request, scope: Scope, send: Se
439439
async def _handle_post_request(self, scope: Scope, request: Request, receive: Receive, send: Send) -> None:
440440
"""Handle POST requests containing JSON-RPC messages."""
441441
writer = self._read_stream_writer
442-
if writer is None: # pragma: no cover
442+
if writer is None: # pragma: lax no cover
443443
raise ValueError("No read stream writer available. Ensure connect() is called first.")
444444
try:
445445
# Validate Accept header
446446
if not await self._validate_accept_header(request, scope, send):
447447
return
448448

449449
# Validate Content-Type
450-
if not self._check_content_type(request): # pragma: no cover
450+
if not self._check_content_type(request): # pragma: lax no cover
451451
response = self._create_error_response(
452452
"Unsupported Media Type: Content-Type must be application/json",
453453
HTTPStatus.UNSUPPORTED_MEDIA_TYPE,
@@ -486,7 +486,7 @@ async def _handle_post_request(self, scope: Scope, request: Request, receive: Re
486486
request_session_id = self._get_session_id(request)
487487

488488
# If request has a session ID but doesn't match, return 404
489-
if request_session_id and request_session_id != self.mcp_session_id: # pragma: no cover
489+
if request_session_id and request_session_id != self.mcp_session_id: # pragma: lax no cover
490490
response = self._create_error_response(
491491
"Not Found: Invalid or expired session ID",
492492
HTTPStatus.NOT_FOUND,
@@ -544,23 +544,23 @@ async def _handle_post_request(self, scope: Scope, request: Request, receive: Re
544544
response_message = event_message.message
545545
break
546546
# For notifications and requests, keep waiting
547-
else: # pragma: no cover
547+
else: # pragma: lax no cover
548548
logger.debug(f"received: {event_message.message.method}")
549549

550550
# At this point we should have a response
551551
if response_message:
552552
# Create JSON response
553553
response = self._create_json_response(response_message)
554554
await response(scope, receive, send)
555-
else: # pragma: no cover
555+
else: # pragma: lax no cover
556556
# This shouldn't happen in normal operation
557557
logger.error("No response message received before stream closed")
558558
response = self._create_error_response(
559559
"Error processing request: No response received",
560560
HTTPStatus.INTERNAL_SERVER_ERROR,
561561
)
562562
await response(scope, receive, send)
563-
except Exception: # pragma: no cover
563+
except Exception: # pragma: lax no cover
564564
logger.exception("Error processing JSON response")
565565
response = self._create_error_response(
566566
"Error processing request",
@@ -626,14 +626,14 @@ async def sse_writer(): # pragma: lax no cover
626626
# Then send the message to be processed by the server
627627
session_message = self._create_session_message(message, request, request_id, protocol_version)
628628
await writer.send(session_message)
629-
except Exception: # pragma: no cover
629+
except Exception: # pragma: lax no cover
630630
logger.exception("SSE response error")
631631
await sse_stream_writer.aclose()
632632
await self._clean_up_memory_streams(request_id)
633633
finally:
634634
await sse_stream_reader.aclose()
635635

636-
except Exception as err: # pragma: no cover
636+
except Exception as err: # pragma: lax no cover
637637
logger.exception("Error handling POST request")
638638
response = self._create_error_response(
639639
f"Error handling POST request: {err}",
@@ -653,7 +653,7 @@ async def _handle_get_request(self, request: Request, send: Send) -> None:
653653
and notifications on this stream.
654654
"""
655655
writer = self._read_stream_writer
656-
if writer is None: # pragma: no cover
656+
if writer is None: # pragma: lax no cover
657657
raise ValueError("No read stream writer available. Ensure connect() is called first.")
658658

659659
# Validate Accept header - must include text/event-stream
@@ -667,7 +667,7 @@ async def _handle_get_request(self, request: Request, send: Send) -> None:
667667
await response(request.scope, request.receive, send)
668668
return
669669

670-
if not await self._validate_request_headers(request, send): # pragma: no cover
670+
if not await self._validate_request_headers(request, send): # pragma: lax no cover
671671
return
672672

673673
# Handle resumability: check for Last-Event-ID header
@@ -740,7 +740,7 @@ async def standalone_sse_writer():
740740
async def _handle_delete_request(self, request: Request, send: Send) -> None:
741741
"""Handle DELETE requests for explicit session termination."""
742742
# Validate session ID
743-
if not self.mcp_session_id: # pragma: no cover
743+
if not self.mcp_session_id: # pragma: lax no cover
744744
# If no session ID set, return Method Not Allowed
745745
response = self._create_error_response(
746746
"Method Not Allowed: Session termination not supported",
@@ -749,7 +749,7 @@ async def _handle_delete_request(self, request: Request, send: Send) -> None:
749749
await response(request.scope, request.receive, send)
750750
return
751751

752-
if not await self._validate_request_headers(request, send): # pragma: no cover
752+
if not await self._validate_request_headers(request, send): # pragma: lax no cover
753753
return
754754

755755
await self.terminate()
@@ -787,7 +787,7 @@ async def terminate(self) -> None:
787787
await self._write_stream_reader.aclose()
788788
if self._write_stream is not None: # pragma: no branch
789789
await self._write_stream.aclose()
790-
except Exception as e: # pragma: no cover
790+
except Exception as e: # pragma: lax no cover
791791
# During cleanup, we catch all exceptions since streams might be in various states
792792
logger.debug(f"Error closing streams: {e}")
793793

@@ -816,7 +816,7 @@ async def _validate_request_headers(self, request: Request, send: Send) -> bool:
816816

817817
async def _validate_session(self, request: Request, send: Send) -> bool:
818818
"""Validate the session ID in the request."""
819-
if not self.mcp_session_id: # pragma: no cover
819+
if not self.mcp_session_id: # pragma: lax no cover
820820
# If we're not using session IDs, return True
821821
return True
822822

@@ -833,7 +833,7 @@ async def _validate_session(self, request: Request, send: Send) -> bool:
833833
return False
834834

835835
# If session ID doesn't match, return error
836-
if request_session_id != self.mcp_session_id: # pragma: no cover
836+
if request_session_id != self.mcp_session_id: # pragma: lax no cover
837837
response = self._create_error_response(
838838
"Not Found: Invalid or expired session ID",
839839
HTTPStatus.NOT_FOUND,
@@ -872,7 +872,7 @@ async def _replay_events(self, last_event_id: str, request: Request, send: Send)
872872
"""
873873
event_store = self._event_store
874874
if not event_store:
875-
return # pragma: no cover
875+
return # pragma: lax no cover
876876

877877
try:
878878
headers = {
@@ -921,9 +921,9 @@ async def send_event(event_message: EventMessage) -> None:
921921
await sse_stream_writer.send(event_data)
922922
except anyio.ClosedResourceError:
923923
# Expected when close_sse_stream() is called
924-
logger.debug("Replay SSE stream closed by close_sse_stream()") # pragma: no cover
924+
logger.debug("Replay SSE stream closed by close_sse_stream()") # pragma: lax no cover
925925
except Exception: # pragma: lax no cover
926-
logger.exception("Error in replay sender") # pragma: no cover
926+
logger.exception("Error in replay sender") # pragma: lax no cover
927927

928928
# Create and start EventSourceResponse
929929
response = EventSourceResponse(
@@ -934,20 +934,20 @@ async def send_event(event_message: EventMessage) -> None:
934934

935935
try:
936936
await response(request.scope, request.receive, send)
937-
except Exception: # pragma: no cover
938-
logger.exception("Error in replay response") # pragma: no cover
937+
except Exception: # pragma: lax no cover
938+
logger.exception("Error in replay response") # pragma: lax no cover
939939
finally:
940940
await sse_stream_writer.aclose()
941941
await sse_stream_reader.aclose()
942942

943-
except Exception: # pragma: no cover
944-
logger.exception("Error replaying events") # pragma: no cover
945-
response = self._create_error_response( # pragma: no cover
946-
"Error replaying events", # pragma: no cover
947-
HTTPStatus.INTERNAL_SERVER_ERROR, # pragma: no cover
948-
INTERNAL_ERROR, # pragma: no cover
949-
) # pragma: no cover
950-
await response(request.scope, request.receive, send) # pragma: no cover
943+
except Exception: # pragma: lax no cover
944+
logger.exception("Error replaying events") # pragma: lax no cover
945+
response = self._create_error_response( # pragma: lax no cover
946+
"Error replaying events", # pragma: lax no cover
947+
HTTPStatus.INTERNAL_SERVER_ERROR, # pragma: lax no cover
948+
INTERNAL_ERROR, # pragma: lax no cover
949+
) # pragma: lax no cover
950+
await response(request.scope, request.receive, send) # pragma: lax no cover
951951

952952
@asynccontextmanager
953953
async def connect(
@@ -1049,6 +1049,6 @@ async def message_router():
10491049
await read_stream.aclose()
10501050
await write_stream_reader.aclose()
10511051
await write_stream.aclose()
1052-
except Exception as e: # pragma: no cover
1052+
except Exception as e: # pragma: lax no cover
10531053
# During cleanup, we catch all exceptions since streams might be in various states
10541054
logger.debug(f"Error closing streams: {e}")

src/mcp/server/transport_security.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,12 @@ def __init__(self, settings: TransportSecuritySettings | None = None):
4343
def _validate_host(self, host: str | None) -> bool:
4444
"""Validate the Host header against allowed values."""
4545
if not host:
46-
logger.warning("Missing Host header in request") # pragma: no cover
47-
return False # pragma: no cover
46+
logger.warning("Missing Host header in request") # pragma: lax no cover
47+
return False # pragma: lax no cover
4848

4949
# Check exact match first
5050
if host in self.settings.allowed_hosts:
51-
return True # pragma: no cover
51+
return True # pragma: lax no cover
5252

5353
# Check wildcard port patterns
5454
for allowed in self.settings.allowed_hosts:
@@ -59,8 +59,8 @@ def _validate_host(self, host: str | None) -> bool:
5959
if host.startswith(base_host + ":"): # pragma: no branch
6060
return True
6161

62-
logger.warning(f"Invalid Host header: {host}") # pragma: no cover
63-
return False # pragma: no cover
62+
logger.warning(f"Invalid Host header: {host}") # pragma: lax no cover
63+
return False # pragma: lax no cover
6464

6565
def _validate_origin(self, origin: str | None) -> bool:
6666
"""Validate the Origin header against allowed values."""
@@ -69,20 +69,20 @@ def _validate_origin(self, origin: str | None) -> bool:
6969
return True
7070

7171
# Check exact match first
72-
if origin in self.settings.allowed_origins: # pragma: no cover
73-
return True # pragma: no cover
72+
if origin in self.settings.allowed_origins: # pragma: lax no cover
73+
return True # pragma: lax no cover
7474

7575
# Check wildcard port patterns
76-
for allowed in self.settings.allowed_origins: # pragma: no cover
77-
if allowed.endswith(":*"): # pragma: no cover
76+
for allowed in self.settings.allowed_origins: # pragma: lax no cover
77+
if allowed.endswith(":*"): # pragma: lax no cover
7878
# Extract base origin from pattern
79-
base_origin = allowed[:-2] # pragma: no cover
79+
base_origin = allowed[:-2] # pragma: lax no cover
8080
# Check if the actual origin starts with base origin and has a port
81-
if origin.startswith(base_origin + ":"): # pragma: no cover
82-
return True # pragma: no cover
81+
if origin.startswith(base_origin + ":"): # pragma: lax no cover
82+
return True # pragma: lax no cover
8383

84-
logger.warning(f"Invalid Origin header: {origin}") # pragma: no cover
85-
return False # pragma: no cover
84+
logger.warning(f"Invalid Origin header: {origin}") # pragma: lax no cover
85+
return False # pragma: lax no cover
8686

8787
def _validate_content_type(self, content_type: str | None) -> bool:
8888
"""Validate the Content-Type header for POST requests."""
@@ -106,11 +106,11 @@ async def validate_request(self, request: Request, is_post: bool = False) -> Res
106106
# Validate Host header
107107
host = request.headers.get("host")
108108
if not self._validate_host(host):
109-
return Response("Invalid Host header", status_code=421) # pragma: no cover
109+
return Response("Invalid Host header", status_code=421) # pragma: lax no cover
110110

111111
# Validate Origin header
112112
origin = request.headers.get("origin")
113113
if not self._validate_origin(origin):
114-
return Response("Invalid Origin header", status_code=403) # pragma: no cover
114+
return Response("Invalid Origin header", status_code=403) # pragma: lax no cover
115115

116116
return None

0 commit comments

Comments
 (0)