fix(core): say why a never-acknowledged subscriptions/listen ended

A modern-era `subscriptions/listen` is a long-lived request: the first
message on its stream must be `notifications/subscriptions/acknowledged`,
and the JSON-RPC `result` for the listen id is reserved as the
graceful-closure marker. A server that answers the listen request with a
bare `result` is saying "acknowledged and closed in the same breath" — the
SDK settles the subscription `graceful` and rejects the pending `listen()`.

The Inspector read that rejection as an ordinary drop: eight re-lists over
roughly a minute, the badge flickering `Reconnecting…`, and a final bare
`Stream ended` that said nothing about why. Accepting the message was
never wrong — it is a well-formed graceful-closure response. The silence
was.

- `core/mcp/subscriptionAck.ts` owns the predicate. The SDK gives this
  close no code of its own — it is the same `SdkError(ConnectionClosed)`
  any pre-ack close carries — so the predicate reads the message, since
  the alternative is refusing to retry a genuinely transient drop.
- The condition is deterministic, so the stream ends on the first
  occurrence instead of spending the whole backoff run. Both routes are
  covered: the one a subscribe/unsubscribe reaches, and the one a
  reconnect reaches when an acknowledged stream drops and the server has
  since started refusing.
- A `"never-acknowledged"` status of its own, not `"ended"` — `ended`
  means an expected close. It surfaces as the thrown error's message, an
  orange "Not acknowledged" badge, and a notice in the panel.
- `subscriptions-never-acknowledged-http.json` reproduces it. It
  acknowledges the first listen that subscribes to a resource and refuses
  every one after, which is what makes the badge reachable by hand.

Closes #2097

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-27 20:49:51 -04:00
commit 8535ae0c2f

View file

@ -291,7 +291,25 @@ v2/main/
│ │ # (`InspectorClientOptions.appElicitation`), and
│ │ # supplying one is what advertises the nested
│ │ # `elicitation` setting — so web opts in and
│ │ # cli/tui, which cannot host an App, do not)
│ │ # cli/tui, which cannot host an App, do not;
│ │ # subscriptionAck.ts: recognizing the modern
│ │ # `subscriptions/listen` a server answers with a
│ │ # bare graceful-closure result instead of
│ │ # acknowledging (#2097). The SDK gives that close
│ │ # NO code of its own — it is the same
│ │ # `SdkError(ConnectionClosed)` any pre-ack close
│ │ # carries — so the predicate reads the message,
│ │ # deliberately, since the alternative is refusing
│ │ # to retry a genuinely transient drop. The live
│ │ # check on that string is the integration test,
│ │ # which drives a real never-acknowledging server
│ │ # (`subscriptions-never-acknowledged-http.json`);
│ │ # an SDK that rephrases it fails there rather than
│ │ # silently reverting to the eight-retry loop. The
│ │ # status is `"never-acknowledged"`, NOT `"ended"`:
│ │ # `ended` means an EXPECTED close, and reading a
│ │ # deterministic conformance failure as one is the
│ │ # silence #2063 reported)
│ │ ├── import/ # Config import strategies (#1348): client-config parsers
│ │ │ # (Claude Desktop/Cursor/Cline/VS Code), registry
│ │ │ # server.json parser, strategy registry + well-known