Skip to content

mcp: keep notifications/cancelled across session Close - #1

Closed
vshalpnjabi wants to merge 1 commit into
mainfrom
cursor/close-after-cancel-notify-9c7c
Closed

vshalpnjabi wants to merge 1 commit into
mainfrom
cursor/close-after-cancel-notify-9c7c

Conversation

@vshalpnjabi

Copy link
Copy Markdown
Owner

Summary

Reserve the outgoing notifications/cancelled before call() returns so a following Close cannot drop it.

Why

Since modelcontextprotocol#1151, cancel retires the call and sends notifications/cancelled from a goroutine. If the session closes right after return, the connection is already shutting down and the call is gone from outgoingCalls, so Notify drops the notification. On stateful streamable HTTP the server handler never sees cancel, and ClientSession.Close waits out closeDeleteTimeout (5s).

What changed

  • Synchronously reserve the outgoing notification (BeginOutgoingNotification) before returning from call()
  • Close waits for that write (bounded by notifyCancellationTimeout); Notify is allowed while the reservation is held
  • Caller still returns immediately; the call is still retired immediately

Tests

  • TestCloseAfterCanceledCallCancelsTheServerHandler — 20/20 (~0ms; was 5s Close + handler never cancelled)
  • TestBeginOutgoingNotificationAllowsNotifyDuringClose — 20/20
  • TestCallCancellation_FastReturn, TestCancellation
  • go test ./... and -race on the new tests

Fixes modelcontextprotocol#1505

Open in Web Open in Cursor 

Since modelcontextprotocol#1151, call() retires a cancelled request and sends
notifications/cancelled from a goroutine. Closing the session
immediately after the call returns marks the jsonrpc2 connection
shutting down with no remaining outgoing calls, so Notify rejects the
write. On a stateful streamable HTTP server the tool handler then keeps
running and ClientSession.Close blocks for closeDeleteTimeout.

Reserve the outgoing notification before returning from call() so Close
waits for the write and Notify is allowed during shutdown. The caller
still returns immediately, and the call is still retired immediately.

For modelcontextprotocol#1505

Co-authored-by: Vishal Punjabi <mr.vishalpunjabi@gmail.com>
@vshalpnjabi

Copy link
Copy Markdown
Owner Author

Opened by mistake against the fork. Upstream PR: modelcontextprotocol#1508

@vshalpnjabi vshalpnjabi closed this Nov 6, 2026
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.

mcp: closing a session right after a cancelled call drops notifications/cancelled, so the server handler keeps running

1 participant