Skip to content

Sending cancellation notification to server based on client anyio.CancelScope status #628

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 31 commits into
base: main
Choose a base branch
from

Conversation

davemssavage
Copy link

@davemssavage davemssavage commented May 4, 2025

Motivation and Context

MCP 2025-03-26 introduces a cancellation notification [1] this patch simplifies the usage of this function for the ClientSession by using the anyio.CancelScope as a trigger to send the cancellation notification and is a potential fix for #160

How Has This Been Tested?

Added unit tests to assert behaviour and tidied up previous work around for lack of this behaviour in tests/shared/test_session.py#test_request_cancellation

Breaking Changes

This patch sends a CancelNotification to any in flight requests to the server at the point the client is cancelled.
Clients that would have expected calls to complete on the server regardless of status of cancel scope on the client will need to be updated.

As a work around for any clients where this behavior is not desired this patch adds a cancellable flag to send_request and call_tool to enable clients to opt out of this behaviour so server tasks must respond prior to exitting.

This cancellable=False behaviour is required for the initialised call from the client as described in [1] though this does open up a raft of questions on what is supposed to happen if the initialised call blocks on the server. A timeout on the client might be appropriate however this opens up follow on questions on what subsequent behaviour should be after an initialisation timeout.

This approach of a breaking change is suggested as author expects in general most clients would prefer to cancel an expensive server tasks if client is cancelled and initialisation tasks are expected to be quick and non-blocking on the server.

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

Additional context

I've added a docstring to describe the behaviour to send request. I suspect wider documentation updates would be sensible, I'm happy to write something if this patch looks useful.

It might also be useful to separate out the idea of client cancelation with server cancelation such that a client can cancel its work but leave the server task running. Comments on the pull request on the best approach is probably necessary.

[1] https://modelcontextprotocol.io/specification/2025-03-26/basic/utilities/cancellation

@davemssavage davemssavage changed the title Add support for sending cancellation in based on anyio.CancelScope status Add support for sending cancellation based on anyio.CancelScope status May 4, 2025
f"{request.__class__.__name__}. Waited "
f"{timeout} seconds."
),
with anyio.CancelScope(shield=not cancellable):
Copy link
Author

@davemssavage davemssavage May 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative to this might be to separate client cancelation and server cancelation, e.g. client can be cancelled and server cancellation event is only set if a flag such as 'propagate_client_cancelation_to_server' (shorter names available) is set on the request.

@davemssavage davemssavage changed the title Add support for sending cancellation based on anyio.CancelScope status Add support for sending cancellation notification to server based on client anyio.CancelScope status May 5, 2025
@davemssavage davemssavage changed the title Add support for sending cancellation notification to server based on client anyio.CancelScope status Sending cancellation notification to server based on client anyio.CancelScope status May 5, 2025
@@ -33,6 +35,12 @@
SendRequestT = TypeVar("SendRequestT", ClientRequest, ServerRequest)
SendResultT = TypeVar("SendResultT", ClientResult, ServerResult)
SendNotificationT = TypeVar("SendNotificationT", ClientNotification, ServerNotification)
SendNotificationInternalT = TypeVar(
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is ugly appears to need some python typechecking sorcery to tidy up though

await client_session.call_tool(
"slow_tool",
cancellable=False,
read_timeout_seconds=timedelta(seconds=10),
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A test that validates a short timeout with an uncancelable call would be sensible

@davemssavage
Copy link
Author

Also potential fix for #230

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant