Skip to content

MCP Python SDK Protocol Compliance Gap-1:Response Sent After Receiving Cancellation Notifications #1419

@younaman

Description

@younaman

Initial Checks

Description

MCP Python SDK Protocol Compliance Gap: Response Sent After Receiving Cancellation Notifications

  • Assessment: This is a protocol “SHOULD not” compliance gap. When acting as the receiver of a cancellation notification, the SDK still sends a JSON-RPC error response for the cancelled request, contrary to the spec’s guidance.

Description

The MCP specification states: “Receivers of cancellation notifications SHOULD not send a response for the cancelled request.”
In the current implementation, after processing a CancelledNotification, the SDK sends an error response for the cancelled in-flight request.

Detailed Analysis

                            # Handle cancellation notifications
                            if isinstance(notification.root, CancelledNotification):
                                cancelled_id = notification.root.params.requestId
                                if cancelled_id in self._in_flight:
                                    await self._in_flight[cancelled_id].cancel()
    async def cancel(self) -> None:
        """Cancel this request and mark it as completed."""
        ...
        self._cancel_scope.cancel()
        self._completed = True  # Mark as completed so it's removed from in_flight
        # Send an error response to indicate cancellation
        await self._session._send_response(
            request_id=self.request_id,
            response=ErrorData(code=0, message="Request cancelled", data=None),
        )
  • Result: Even when the cancellation is triggered by a CancelledNotification, the SDK responds with a JSON-RPC error for the cancelled request, diverging from the spec’s “SHOULD not” recommendation.

Potential Impact

  • Protocol consistency: Behavior deviates from the spec’s guidance, potentially surprising peers that expect silent termination.
  • Interoperability: Differences may surface when integrating with implementations that strictly follow the “SHOULD not” behavior.

Remediation

  • Implement silent cancellation for notification-triggered cancellations:
    • Add a non-responding cancellation path on RequestResponder (e.g., mark_cancelled_without_response()), which cancels and marks complete without calling _send_response.
    • In the cancellation notification handling branch, call the silent path instead of cancel().

Suggested Implementation

  • Location: src/mcp/shared/session.py in BaseSession._receive_loop() cancellation branch and in RequestResponder.

Change call site to silent cancellation:

  • From:
    • await self._in_flight[cancelled_id].cancel()
  • To:
    • await self._in_flight[cancelled_id].mark_cancelled_without_response()

Impact

  • Aligns behavior with the MCP spec’s “SHOULD not” guidance.
  • Reduces unnecessary response traffic and improves predictability and interoperability in cancellation flows.

Example Code

Python & MCP Python SDK

Latest

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Nice to haves, rare edge casesfeature requestRequest for a new feature that's not currently supportedimproves spec complianceWhen a change improves ability of SDK users to comply with spec definitionready for workEnough information for someone to start working on

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions