Skip to content

feat(client): cancel an abandoned tools/call, prompts/get or resources/read - #1344

Merged
DaleSeo merged 3 commits into
modelcontextprotocol:mainfrom
talhayme:feat/1338-cancel-abandoned-mrtr-round
Oct 16, 2026
Merged

DaleSeo merged 3 commits into
modelcontextprotocol:mainfrom
talhayme:feat/1338-cancel-abandoned-mrtr-round

Conversation

@talhayme

Copy link
Copy Markdown
Contributor

Closes #1338.

A caller that drops call_tool (a tokio::time::timeout, a lost select! branch) abandoned the request locally while the server kept working on it. This sends a best-effort notifications/cancelled for the request in flight when that happens.

How. Option 1 from the issue, with the constraints from the discussion there: a small drop guard, modelled on PendingSubscriptionRequest, that holds the request id and the peer. It is armed while the response is awaited and disarmed as soon as the response arrives, so dropping the future while the client handler fulfils input requests cancels nothing.

One difference from what I wrote in the issue. The guard sits in call_tool_once / get_prompt_once / read_resource_once rather than in the MRTR loops, because read_resource_once wraps the send in cache handling that I didn't want to duplicate. The loops get it through those calls. It also means a dropped *_once future is cancelled too. Say if you'd rather keep it to the loops.

Tests (tests/test_abandoned_request_cancel.rs): abandoned tool call; abandoned retry round (the retry's id is the one cancelled); prompt and resource; drop during local input handling sends nothing; completed call sends nothing. The first three fail without the change.

No public API change. cargo test --all-features, the non-local feature set, clippy and cargo +nightly fmt pass.

AI disclosure: written with Claude Code; I reviewed the change and ran the checks above.

🤖 Generated with Claude Code

…s/read

A caller that drops `call_tool` (a `tokio::time::timeout`, a lost `select!`
branch) abandoned the request locally while the server kept working on it.
Send a best-effort `notifications/cancelled` for the request in flight when
that happens.

A drop guard, modelled on `PendingSubscriptionRequest`, is armed while the
response is awaited and disarmed as soon as it arrives, so dropping the
future while the client handler fulfils MRTR input requests cancels nothing.

Closes modelcontextprotocol#1338

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@talhayme
talhayme requested a review from a team as a code owner October 12, 2026 07:46
@github-actions github-actions Bot added T-dependencies Dependencies related changes T-test Testing related changes T-config Configuration file changes T-core Core library changes T-service Service layer changes labels Oct 12, 2026
Comment thread crates/rmcp/src/service/client.rs Outdated
Comment thread crates/rmcp/tests/test_abandoned_request_cancel.rs Outdated
Comment thread crates/rmcp/src/service/client.rs
Every request the client peer sends now goes through the drop guard, not
only tools/call, prompts/get and resources/read. `initialize` is not
sent through `Peer`, and the helper is client-only: a client must never
cancel `initialize`, and from 2026-07-28 a server may send
`notifications/cancelled` only to end a subscriptions/listen stream.

The guard no longer unregisters a subscription that these requests never
register, and the test helper fails when the call never reaches the
point it is to be abandoned at instead of dropping the call anyway.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread crates/rmcp/src/service/client.rs Outdated
Comment thread crates/rmcp/src/service/client.rs Outdated
Comment thread crates/rmcp/src/service/client.rs Outdated
… in the guard

Per review: server/discover is part of the handshake like initialize, so
it goes back to plain send_request. AbandonedRequestGuard borrows the
peer instead of cloning it, and the helper's doc keeps only the
constraint a future editor must not break.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@DaleSeo
DaleSeo merged commit ce3a57a into modelcontextprotocol:main Oct 16, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T-config Configuration file changes T-core Core library changes T-dependencies Dependencies related changes T-service Service layer changes T-test Testing related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Client: an abandoned call_tool sends no notifications/cancelled — the MRTR helper hides each round's RequestHandle

2 participants