Servers come and go

Can a host pick up a new server mid-session? What happens when one dies and comes back? The protocol answers part of this; the host answers the rest.

By the end of this lesson you can

  • say how a host learns about a new server, and why the protocol never announces one;
  • trace a live tool change from list_changed to the model's next turn;
  • predict what an outage and a restart do, in the legacy and the modern era;
  • explain the gateway pattern and explicit state handles.

A session with churn

What the model sees, turn by turn

Tools in the model's next prompt mcp__A__deploymcp__A__rollback mcp__A__deploymcp__A__rollbackhidden while A is down mcp__A__deploymcp__A__rollback mcp__B__search_docs mcp__B__summarize_doc mcp__C__query_billing new tools appear on the next turn Hostholds theconversation Server A Server A: downcalls → tool errors Server B deploys summarize_doc Server Cadded by an admin listen streams notifications/tools/list_changed from B client calls tools/liston B again host was told (config)discover · list · listen conversation survivesB and C carry on no session to rebuildre-listen, then re-list
  1. The session starts with servers A and B. Each has a client, a tool list, and an open subscriptions/listen stream.
  2. B deploys a new tool and sends notifications/tools/list_changed on its listen stream.
  3. The client calls tools/list on B again. The new tool is in the model's next prompt, never mid-turn.
  4. An admin adds server C to the config (or a gateway). Nothing in the protocol announced it: the host was told, and opens a new client.
  5. A dies. Its listen stream drops, its tools are hidden, and any call to it comes back to the model as a tool error. The conversation lives in the host, so it survives.
  6. A is back. There is no session to rebuild: the client re-opens its listen stream and re-lists, because notifications sent while A was down are gone.

The five rules

// what B sends on the open listen stream
{"method":"notifications/tools/list_changed","params":{"_meta":{"io.modelcontextprotocol/subscriptionId":7}}}

Play out an outage

Press the buttons in any order. Take a model turn after each event to see what the model would experience.

Servers, as the host sees them

    Tools in the model's next prompt

      What happened

      Restarts, era by era

      A server restarts while…Legacy, up to 2025-11-25Modern, 2026-07-28
      idleSession id unknown to the new process: 404, client must initialize againNothing to do. Next request succeeds
      a call is in flightCall lost; resumable only if the server kept an event storeCall lost; client retries it (stdio: restart the process first)
      waiting for user inputPending server request lost with the sessionClient still holds requestState; retry succeeds if the key is unchanged and the ticket unexpired
      a listen stream is openGET stream drops; reconnect, maybe resumeStream drops with no completion result; re-send subscriptions/listen, then re-list
      the user's token is validStill valid: issued by the authorization serverStill valid: issued by the authorization server

      In practice: a gateway

      The gateway pattern

      Claude IDE Gatewayone MCP endpoint github jira billing billing: unhealthyhidden by the gateway list_changed to every listener
      1. Hosts connect to one MCP endpoint, the gateway. It fans out to many servers behind it.
      2. It health-checks them. When one fails, the gateway hides its tools.
      3. It emits tools/list_changed to every listening client when servers join or leave, so hosts re-list on their own.

      Key idea

      In an enterprise, put a gateway in front. Hosts connect to one MCP endpoint; the gateway fans out to many servers, health-checks them, hides a failed one, and emits tools/list_changed to every listening client when servers join or leave. That is the practical form of hot-swapping, and it is an architecture, not a protocol feature.

      Explicit state handles

      One more design rule follows from statelessness: if a tool needs state across calls, a cart or an open browser, it returns an explicit handle that the model passes back, and the server stores the state under it. Such handles survive restarts and replicas; connection state would not.

      // create_basket returns a handle…
      { "content": [{ "type": "text", "text": "{\"basket_id\": \"bsk_81f2\"}" }] }
      // …which the model passes to the next call; the server keeps the basket in its database
      { "name": "add_item", "arguments": { "basket_id": "bsk_81f2", "sku": "A-100" } }

      Quick check

      A new MCP server starts on your network. How does the host find out?

      • The server broadcasts itself
      • The client polls a registry with server/discover
      • It doesn't, until it is told through config, an admin console or a gateway
      The protocol has no runtime discovery of servers. server/discover describes a server you already know.

      Server B adds a tool while the model is mid-answer. When can the model use it?

      • Immediately, mid-answer
      • On its next turn, once the host has re-listed and rebuilt the prompt
      • Only in a new conversation
      The tool list is rebuilt for each model call. Hosts often batch changes because each one invalidates the prompt cache.

      A modern server restarts while idle. What must the client do?

      • Send initialize again
      • Nothing for ordinary calls; re-open any listen stream and re-list
      • Ask the user to log in again
      No session existed, so nothing was lost. Only the listen stream needs re-opening, and missed notifications are not replayed, so re-list. A legacy server would answer 404 and need a new initialize.

      Recap