Per the MCP cancellation spec, receivers of notifications/cancelled must
not send a response. RequestResponder.cancel() was sending an error
response back to the sender, causing strict clients (e.g. Claude Code)
to log "Received a response for an unknown message ID" and reconnect on
every long-running tool cancellation.
Remove the _send_response call from RequestResponder.cancel(). Add
sender-side cleanup in send_notification so send_request() returns
immediately when a CancelledNotification is sent, matching the
TypeScript SDK's behavior.
Fixes modelcontextprotocol#2480
Summary
Motivation and Context
RequestResponder.cancel() was sending ErrorData(code=0, message="Request cancelled") back to the sender after receiving notifications/cancelled. The cancellation spec says receivers SHOULD NOT do this. Claude Code, for example, logs "Received a response for an unknown message ID" and reconnects every time a long-running tool gets cancelled.
The TypeScript SDK already gets this right: the sender rejects its own pending promise locally when it sends the cancellation (source), and the receiver just calls controller.abort() without sending anything back. This PR brings the Python SDK in line with that behavior.
How Has This Been Tested?
Breaking Changes
Code that inspects e.code on a cancellation MCPError will see -32001 (REQUEST_TIMEOUT) instead of 0. The message "Request cancelled" is unchanged. The TypeScript SDK uses ErrorCode.RequestTimeout for this same case, so the behavior now matches across SDKs.
Types of changes
Checklist
Fixes #2480