Fix #277: MCP tool calls honor abort_controller (ESC-cancel)#308
Open
ericleepi314 wants to merge 1 commit into
Open
Fix #277: MCP tool calls honor abort_controller (ESC-cancel)#308ericleepi314 wants to merge 1 commit into
ericleepi314 wants to merge 1 commit into
Conversation
ESC during a long-running MCP tool hung until the request timeout (5 min default) — the JSON-RPC future had no cancellation path. - McpClient._send_request/call_tool accept an abort signal; the listener hops onto the request loop via call_soon_threadsafe to cancel the pending future, so ESC from the UI thread unblocks the call in ~0.1s - on abort, a bounded (2s) best-effort MCP notifications/cancelled is sent so compliant servers stop the work; a wedged transport cannot block the unblock path - listener + pending-entry cleanup runs in a finally that also covers a send that raises or is cancelled mid-await - the receive loop skips responses for already-cancelled futures (set_result on a cancelled future would kill the loop and nuke every pending request) - the tool wrapper re-raises AbortError past its except-Exception so dispatch renders the user-cancel message, and resolves the signal via getattr for duck-typed contexts Closes #277, closes #171 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This was referenced Jun 12, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #277
Closes #171 (duplicate)
Summary
ESC during a long-running MCP tool hung until the MCP request timeout (5 min default). The pending JSON-RPC future now has a real cancellation path:
McpClient._send_request/call_toolaccept anabort_signal. The abort listener fires on the aborting thread (TUI ESC handler) and hops onto the request loop viacall_soon_threadsafe(future.cancel)— the call unblocks in ~0.1s.notifications/cancelledis sent best-effort with the in-flightrequestIdso a compliant server stops the work — bounded to 2s so a fully wedged transport (the likely cause of the hang being escaped) can't block the unblock path.finallypops the pending entry and removes the listener across all exit paths — success, timeout, abort, genuine task-cancel, and atransport.sendthat raises or is cancelled mid-await (the send was moved inside thetryafter review caught the leak).InvalidStateErrorand kill the loop (nuking every pending request). It's now skipped via adone()check.AbortErrorpast itsexcept Exceptionso the dispatch layer renders the user-cancel message instead of a generic tool error; session-expiry retry threads the signal too.Scope note:
list_tools/list_resources/initializeare startup-time, never on the interactive ESC path — deliberately not signal-threaded.Test plan
tests/test_mcp_abort.py: abort unblocks <2s on a hanging transport; cancellation notification carries the rightrequestId(andid=None); pending cleaned; pre-aborted sends nothing; listener removed on normal completion; send-failure cleans listener+pending; receive loop survives a late response for a cancelled future; wrapper re-raises AbortError🤖 Generated with Claude Code