skip to content

On stdio, what must an MCP client do about subscriptions/listen after a reconnect?

level: seniorimportance: should knowfreq 32%

answer

  1. a restarted process remembers nothing
  2. nothing is restored for you
  3. the client re-issues the request
  4. and nothing missed is replayed
  5. so resynchronise the lists too

basics

~20 s

It MUST re-send subscriptions/listen. On the stdio transport a reconnect means a new server process, which carries nothing over from the old one, so any prior subscription is gone. Nothing is replayed, so the client should also re-fetch the lists it cares about.

solid answer

~40 s

In MCP revision 2026-07-28 a subscription is scoped to the request that created it, and on stdio a reconnect means the client relaunched or reattached to a server process that knows nothing about the previous one. So the rule is explicit: after a reconnect the client MUST re-send `subscriptions/listen` with the filter it wants. Nothing is restored automatically — an open connection is not a session, and 2026-07-28 has no resumability at all, since `Last-Event-ID` and SSE event ids were removed. That means changes that happened while no server was running, or between the crash and the re-listen, are simply lost. The safe pattern is: relaunch, re-send `subscriptions/listen`, wait for `notifications/subscriptions/acknowledged`, then re-fetch the lists you care about so your caches and the push channel are back in agreement.

go deeper

for a junior

Remember that a restarted stdio server keeps nothing, so the client has to ask for its subscription again by sending subscriptions/listen a second time.

for a middle

Explain why: an open stdio connection is not a session in 2026-07-28, the old request died with the old process, and there is no resumability to fall back on.

for a senior

Show the full recovery sequence — relaunch, re-listen, wait for the acknowledgement, then re-fetch the lists — and name the stale-cache bug that skipping the last step produces.

for a principal

Own it as client-library design: desired subscriptions as declarative state, a supervisor that re-establishes them on every connect, backoff so a crash-looping server does not become a spawn storm.

## The rule On the stdio transport, after a reconnect the client **MUST** re-send `subscriptions/listen`. This is stated as a client obligation in MCP revision **2026-07-28**, and it follows directly from the era's core framing: MCP is a stateless protocol, every request is self-contained, and "an open connection, such as a STDIO process, is not a conversation or session". ## Why stdio makes this vivid On stdio, an MCP server is a subprocess the client launched, communicating over newline-delimited JSON-RPC on stdin/stdout. "Reconnect" therefore usually means one of two things: - the server process crashed or exited and the client started a new one, or - the host restarted and relaunched its configured servers. Either way the new process has no memory of the previous one. Any in-flight requests died with it, including the long-outstanding `subscriptions/listen` request. There is no identifier the client could present to claim the old subscription — protocol-level sessions and the `Mcp-Session-Id` header were removed in 2026-07-28 precisely so that no such implicit continuity exists. The stdio channel also shapes how a subscription lives while it is running. There is one shared stdin/stdout pair, not a stream per request, so the listen "stream" is simply an outstanding request whose notifications interleave with everything else on the channel. That is also why cancelling it uses `notifications/cancelled` rather than closing a per-request stream. ## What is lost, and how to recover There is **no replay and no resumability** in 2026-07-28. `Last-Event-ID` and SSE event ids were removed, and nothing in the protocol lets a client say "catch me up from here". Everything that changed during the outage — a tool appearing, a watched file being edited — is invisible to the client afterwards, unless it goes and looks. So the recovery sequence is: 1. Relaunch or reattach to the server process. 2. Send `subscriptions/listen` again with the filter you want. Remember that on stdio, version and capabilities travel in `_meta` on this request as on every other one — there is no header layer and no handshake to establish them. 3. Wait for `notifications/subscriptions/acknowledged`, the mandatory first message, before treating push as live. 4. Re-fetch whatever the subscription was protecting: `tools/list`, `prompts/list`, `resources/list`, or the specific resources you were watching. This closes the gap the outage opened. 5. Discard any late messages carrying the old `io.modelcontextprotocol/subscriptionId`, if your client can still receive them. Step 4 is the one implementers skip and then debug for a day: the subscription is technically re-established, the acknowledgement arrived, and yet the palette shows tools that no longer exist because the `list_changed` notification fired while the process was down. ## The same rule in different clothes on HTTP The stdio rule is spelled out explicitly because a subprocess restart is the common case there, but the underlying principle is transport-independent. On Streamable HTTP a broken response stream also loses the in-flight request, and the client must issue a **new** request with a **new** JSON-RPC id rather than resuming — again because resumability was removed. A listen stream is just the longest-lived instance of that rule. ## Design consequences for clients A client library should own subscription re-establishment rather than leaving it to feature code. In practice that means keeping the desired filter as declarative state ("I want tool-list changes from server X and updates to these three files"), and having a supervisor that, whenever a connection is established, drives the sequence: listen, await acknowledgement, resynchronise caches. Feature code should never have to remember to re-subscribe. Backoff matters too. A server that crashes on startup will be relaunched, subscribed to, and crashed again in a tight loop unless the client backs off, and on stdio that loop is process spawning, not just socket dialling. ## Interview framing Say the MUST plainly, then justify it from statelessness: a stdio connection is not a session, so nothing carries over, and there is no resumability to lean on. Then add the part that separates an implementer from a reader — re-listing after re-subscribing, because the gap is not replayed. That sequencing answer is what a senior interviewer is listening for.

  • Why can't the client resume the old subscription instead of re-sending?
    Because there is nothing to resume with. Protocol-level sessions and Mcp-Session-Id were removed in 2026-07-28, and SSE event ids and Last-Event-ID went with them, so no identifier lets a client claim a prior subscription or ask for a catch-up. A restarted stdio process is a clean slate, and the subscription request must be made again.
  • What breaks if the client re-subscribes but skips re-listing?
    Its caches silently diverge. Any list_changed or resources/updated notification that fired while the process was down is never replayed, so the client keeps showing a stale tool palette or stale file content until the next unrelated change happens to fire a notification. Re-fetching right after the acknowledgement closes that gap.
  • Does the same reasoning apply to a broken stream on Streamable HTTP?
    Yes. A broken response stream loses the in-flight request, and 2026-07-28 has no resumability, so the client issues a new request with a new JSON-RPC id rather than resuming. A listen stream is just the longest-lived case, and the same re-subscribe-then-resynchronise sequence applies.

saying these in an interview costs you the question

  • Assuming the server restores subscriptions after a restart
  • Trying to resume with Last-Event-ID, removed in 2026-07-28
  • Treating a stdio connection as a session that carries state
  • Re-subscribing but not re-fetching the lists
  • Retrying process launch in a tight loop with no backoff

context