fix: cancel a Streamable HTTP call by closing its stream (#2140)

The Cancel button POSTed `notifications/cancelled` on every transport. On
a 2026-07-28 Streamable HTTP connection that is the wrong signal — the
spec makes closing the request's SSE response stream the cancellation
signal there and says the notification is neither required nor expected —
so a spec-compliant server acknowledged it 202 and dropped it while the
tool ran to completion.

The SDK already implements the fork, off `transport.hasPerRequestStream`.
Every Inspector connection is wrapped in `MessageTrackingTransport`, which
did not forward that flag, so the SDK read `undefined` and took the stdio
branch on every client — the CLI and TUI included, whose real
`StreamableHTTPClientTransport` advertises it correctly.

The web client needed three more links: its browser transport answers for
the upstream transport that lives on the backend, so it advertises the
flag for `streamable-http` and applies the SDK's `requestSignal` to its
`POST /api/mcp/send`; and `/api/mcp/send`, held open for the whole call,
forwards the browser's disconnect to the upstream `transport.send`.

The test fixture's `toWebRequest` built a Web Request with no `signal`, so
no fixture server could observe a disconnect at all — without that, a test
asserting the cancel reaches the server passes vacuously.

stdio and SSE keep `notifications/cancelled`: they multiplex every request
over one shared channel and have no per-request stream to close. So does
the legacy era, which predates the mechanism.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013hzwzS8UvBsr4yZm7o7sev
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-28 00:55:29 -04:00
commit 85c5206895
2 changed files with 654 additions and 20 deletions

View file

@ -390,7 +390,11 @@ When a tool execution triggers sampling or elicitation requests, they appear inl
- Step description if provided
- Elapsed time display
- Execute button with loading state
- **Cancel button** sends `notifications/cancelled`
- **Cancel button** sends the transport-appropriate cancellation signal (#2140):
on a 2026-07-28 Streamable HTTP connection it aborts the request's own SSE
response stream, which the spec makes the cancellation signal; on stdio (and
on any pre-2026 connection, where the per-stream mechanism does not exist) it
sends `notifications/cancelled`
- **Inline Client Request Queue** - When tool triggers sampling/elicitation:
- Pending requests shown inline (not as separate modal)
- Queue counter shows total pending requests

View file

@ -110,7 +110,11 @@ When a tool execution triggers sampling or elicitation requests, they appear inl
- Step description if provided
- Elapsed time display
- Execute button with loading state
- **Cancel button** sends `notifications/cancelled`
- **Cancel button** sends the transport-appropriate cancellation signal (#2140):
on a 2026-07-28 Streamable HTTP connection it aborts the request's own SSE
response stream, which the spec makes the cancellation signal; on stdio (and
on any pre-2026 connection, where the per-stream mechanism does not exist) it
sends `notifications/cancelled`
- **Inline Client Request Queue** - When tool triggers sampling/elicitation:
- Pending requests shown inline (not as separate modal)
- Queue counter shows total pending requests