Title: fix: send notifications/cancelled on request timeout and caller cancellation by dragogargo · Pull Request #2514 · modelcontextprotocol/python-sdk · GitHub
Open Graph Title: fix: send notifications/cancelled on request timeout and caller cancellation by dragogargo · Pull Request #2514 · modelcontextprotocol/python-sdk
X Title: fix: send notifications/cancelled on request timeout and caller cancellation by dragogargo · Pull Request #2514 · modelcontextprotocol/python-sdk
Description: Motivation and Context Fixes #2507. ClientSession.send_request() never emits a notifications/cancelled message when a request is interrupted — either by the SDK's own anyio.fail_after timeout or by external cancellation (e.g. asyncio.wait_for). The MCP spec requires the sender to issue this notification on timeout, and cooperative cancellation likewise needs it. Impact: Server-side tool coroutines remain suspended after a client cancellation, holding DB connections, cursors, locks, and file handles until the session ends. With long-lived sessions, every cancelled call is a silent resource leak. The issue author demonstrated 50 cancelled call_tool invocations → 50 leaked server-side coroutines. The server already handles CancelledNotification correctly (cancels the in-flight request). Only the client-side emit is missing. Changes Timeout path (TimeoutError): send notifications/cancelled before raising MCPError External cancellation path (CancelledError): catch anyio.get_cancelled_exc_class(), send notification, re-raise _send_cancelled_notification helper: builds and sends the notification inside a shielded cancel scope to guarantee delivery even when called from a cancelled task Two regression tests: one for each cancellation path, using in-memory streams How Has This Been Tested? test_send_request_sends_cancelled_notification_on_timeout — verifies the notification is sent when read_timeout_seconds triggers test_send_request_sends_cancelled_notification_on_caller_cancel — verifies the notification is sent when the caller cancels the task externally All 10 existing test_session.py tests pass Breaking Changes No. The only behavioral change is that the client now sends an additional notification that the server already expects and handles. Types of changes Bug fix (non-breaking change which fixes an issue) New feature (non-breaking change which adds functionality) Breaking change (fix or feature that would cause existing functionality to change) Documentation update Checklist I have read the MCP Documentation My code follows the repository's style guidelines New and existing tests pass locally I have added appropriate error handling I have added or updated documentation as needed
Open Graph Description: Motivation and Context Fixes #2507. ClientSession.send_request() never emits a notifications/cancelled message when a request is interrupted — either by the SDK's own anyio.fail_after timeout o...
X Description: Motivation and Context Fixes #2507. ClientSession.send_request() never emits a notifications/cancelled message when a request is interrupted — either by the SDK's own anyio.fail_after timeo...
Opengraph URL: https://github.com/modelcontextprotocol/python-sdk/pull/2514
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:dcd2fa3b-ac00-39d6-01b4-937a9bbc4ff7 |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | 8560:1ABD8E:88C2C1:B28B23:6A5C138B |
| html-safe-nonce | d6044290d04f09fe150fcc2c70903bf7fd6e58bf4522bd32157fe56010375687 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI4NTYwOjFBQkQ4RTo4OEMyQzE6QjI4QjIzOjZBNUMxMzhCIiwidmlzaXRvcl9pZCI6IjY0MTI2NTA4OTA3MjY3NDA4NzYiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | b0ae27cf3e1d2f1ede4cfc7b35e63be63d1abaed027939e897cc709ca965901f |
| hovercard-subject-tag | pull_request:3592938785 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/modelcontextprotocol/python-sdk/pull/2514/files |
| twitter:image | https://avatars.githubusercontent.com/u/262195167?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/262195167?s=400&v=4 |
| og:image:alt | Motivation and Context Fixes #2507. ClientSession.send_request() never emits a notifications/cancelled message when a request is interrupted — either by the SDK's own anyio.fail_after timeout o... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 5290d7e14309ad1e76106a9c4237bd1041517e83ea182c8ab756752cb0c6940b |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/modelcontextprotocol/python-sdk git https://github.com/modelcontextprotocol/python-sdk.git |
| octolytics-dimension-user_id | 182288589 |
| octolytics-dimension-user_login | modelcontextprotocol |
| octolytics-dimension-repository_id | 862584018 |
| octolytics-dimension-repository_nwo | modelcontextprotocol/python-sdk |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 862584018 |
| octolytics-dimension-repository_network_root_nwo | modelcontextprotocol/python-sdk |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 9c975978430e9ad293956f2bbdaf153b1bd84a99 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width