skip to content

What must an MCP server send first on a subscriptions/listen stream?

level: middleimportance: should knowfreq 48%

answer

  1. silence would otherwise be ambiguous
  2. a mandatory first message
  3. name it exactly, path and all
  4. then every push is tagged
  5. tag lives in _meta, not a header

basics

~20 s

The server MUST send notifications/subscriptions/acknowledged before any change notification. It confirms the stream is live and the filter accepted. Every notification the server then sends on that stream carries io.modelcontextprotocol/subscriptionId in its _meta so the client can tell its streams apart.

solid answer

~40 s

In MCP revision 2026-07-28 the first message on a `subscriptions/listen` response stream MUST be `notifications/subscriptions/acknowledged`. Without it a client cannot distinguish an established subscription from a quiet one: change notifications are sporadic by nature, so silence would be ambiguous between "nothing has changed yet" and "the server never accepted my filter". The acknowledgement makes the stream's readiness explicit and gives the client a clean point to start relying on push instead of polling. From then on, every notification the server delivers over that stream carries `io.modelcontextprotocol/subscriptionId` in `params._meta`. That identifier is what lets a client with more than one open stream — or one that has re-opened a stream after a reconnect — attribute an incoming notification to the right subscription, since MCP has no protocol-level session to do that for it.

code

json · 9 lines
json
{
  "jsonrpc": "2.0",
  "method": "notifications/tools/list_changed",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/subscriptionId": "sub-8f21"
    }
  }
}

go deeper

for a junior

Remember that the server speaks first on a subscription stream, with an acknowledgement message, and that later pushes carry an identifier the client can match.

for a middle

Name notifications/subscriptions/acknowledged precisely, say it is required before any change notification, and explain that it disambiguates a healthy quiet stream from one that never took effect.

for a senior

Show the client-side discipline: time out on a missing acknowledgement, re-list once it arrives to close the gap, and drop late notifications tagged for an abandoned subscription id.

for a principal

Frame it as the cost of statelessness — with sessions gone, readiness and correlation must be carried explicitly in messages, and your client library should encode that as a reusable subscription lifecycle rather than per-feature ad hoc code.

## The two obligations on the stream Opening a `subscriptions/listen` stream in MCP revision **2026-07-28** puts two hard requirements on the server: 1. It **MUST** send `notifications/subscriptions/acknowledged` as the first message on the stream, before any change notification. 2. Every notification it subsequently sends on that stream **carries** `io.modelcontextprotocol/subscriptionId` in `_meta`. Both exist for the same underlying reason: MCP no longer has protocol-level sessions, so nothing outside the messages themselves establishes context. ## Why the acknowledgement is mandatory Change notifications are inherently sporadic. A server's tool list might not change for hours. That makes silence on a freshly-opened stream deeply ambiguous for the client — it could mean the subscription is healthy and nothing has happened, or that the request never really took effect (a proxy buffered it, the server ignored an unknown filter field, an intermediary is holding the response). The mandatory acknowledgement removes the ambiguity. Once a client sees `notifications/subscriptions/acknowledged`, it knows the round trip completed end to end, that any streaming intermediaries are actually flushing, and that it can now stop polling and rely on push. Before it arrives, a careful client keeps its fallback behaviour (re-listing on a timer or on cache expiry) in place. It is also a useful liveness probe at connect time: if the acknowledgement does not arrive within a client-chosen timeout, the sensible move is to tear the stream down and retry rather than to sit indefinitely on a stream that may never deliver anything. ## Why every notification is tagged `io.modelcontextprotocol/subscriptionId` is a reserved `_meta` key, alongside `progressToken` and the W3C Trace Context keys `traceparent`, `tracestate` and `baggage`. The server assigns the identifier for the subscription, and every notification pushed on that stream repeats it. The tagging matters because the client may hold several streams at once — different filters, or a stream re-opened after a reconnect while a stale one is still draining. JSON-RPC notifications have no `id` field (that is what makes them notifications), so without the `_meta` tag there is nothing in the message tying it to a particular subscription. And there is no session identifier to fall back on: protocol-level sessions and the `Mcp-Session-Id` header were removed in 2026-07-28, so `_meta` is the only place such correlation can live. The practical failure mode the tag prevents: a client re-opens its listen stream after a reconnect, the old stream has not fully closed, and a late notification from the dead subscription is attributed to the new one. With the id present, the client can discard messages whose subscription it has already abandoned. ## What the acknowledgement is not It is not a handshake in the old sense. MCP removed `initialize` and `notifications/initialized` in 2026-07-28; the acknowledgement is scoped to one subscription stream, not to a connection, and it establishes no state that later requests may rely on. Version and capabilities still travel in `_meta` on every request, including this one. It is also not the end-of-stream marker. A graceful server-side close is an empty `SubscriptionsListenResult` — the result of the original `subscriptions/listen` request. The acknowledgement is a notification at the head of the stream; the result is what terminates it. And it is not a delivery guarantee for anything that happened before it. Notifications are not replayed, and there is no resumability in 2026-07-28 — `Last-Event-ID` and SSE event ids were removed. Anything that changed while the client had no stream open is simply missed, which is why the safe client pattern is: open the stream, wait for the acknowledgement, then re-list once so that the freshly-fetched catalogue and the push channel are known to be consistent. ## Client checklist - Open `subscriptions/listen` with the filter you want. - Wait for `notifications/subscriptions/acknowledged`; time out and retry if it does not arrive. - Re-fetch the lists you care about, so nothing missed before the stream opened lingers in your cache. - Record the `io.modelcontextprotocol/subscriptionId` from incoming notifications and drop anything tagged for a subscription you have abandoned. ## Interview framing Name the message exactly — `notifications/subscriptions/acknowledged` — say it is a MUST and must precede any change notification, and explain the ambiguity it resolves. Then add the tagging rule and tie both to the absence of sessions in 2026-07-28. That pairing (explicit acknowledgement plus explicit identifier, because there is no implicit session) is the reasoning an interviewer is listening for.

  • What should a client do if the acknowledgement never arrives?
    Treat it as a failed subscription: time out, close the stream, and retry — while keeping whatever fallback it had, such as re-listing on cache expiry. Sitting on an unacknowledged stream is the trap, because a healthy subscription and a stalled one look identical when no changes are happening.
  • Why can't the client just use the connection to correlate notifications?
    Because 2026-07-28 removed protocol-level sessions and the Mcp-Session-Id header, and an open connection is explicitly not a conversation. Notifications also have no JSON-RPC id. The only correlation carrier left is io.modelcontextprotocol/subscriptionId in _meta, which is why every pushed notification repeats it.
  • Does the acknowledgement replay changes the client missed before it opened the stream?
    No. There is no replay and no resumability — SSE event ids and Last-Event-ID were removed in 2026-07-28. Anything that changed while no stream was open is simply lost, so a client should re-fetch the lists it cares about right after the acknowledgement to resynchronise its caches.

saying these in an interview costs you the question

  • Calling the acknowledgement an initialize-style handshake
  • Assuming the first change notification confirms the stream is live
  • Expecting missed notifications to be replayed on connect
  • Looking for the subscription id in an HTTP header
  • Thinking the acknowledgement ends or closes the stream

context