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
The session starts with servers A and B. Each has a client, a tool list, and an open subscriptions/listen stream.
B deploys a new tool and sends notifications/tools/list_changed on its listen stream.
The client calls tools/list on B again. The new tool is in the model's next prompt, never mid-turn.
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.
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.
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
A new server is never discovered by the protocol. There is no broadcast and no registry lookup at run time. The host has to be told, through config, an admin console or a gateway, and then it opens a new client.
A server's own tools can change live. It declares tools.listChanged; the client keeps a subscriptions/listen stream open with toolsListChanged: true; on notifications/tools/list_changed the client calls tools/list again.
The model learns on its next turn, not mid-thought. The host rebuilds the tool list for every model call, so a change shows up at the next request. Changing the list also invalidates the prompt cache, so hosts batch changes.
A server going down fails calls, not the conversation. The conversation lives in the host. Calls to that server fail and come back to the model as errors; the other servers carry on.
Coming back costs nothing in 2026-07-28. There is no session to rebuild: the next request just works. The client re-opens its listen stream and re-lists, because missed notifications are not replayed (no resumability).
// 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-25
Modern, 2026-07-28
idle
Session id unknown to the new process: 404, client must initialize again
Nothing to do. Next request succeeds
a call is in flight
Call lost; resumable only if the server kept an event store
Call lost; client retries it (stdio: restart the process first)
waiting for user input
Pending server request lost with the session
Client still holds requestState; retry succeeds if the key is unchanged and the ticket unexpired
a listen stream is open
GET stream drops; reconnect, maybe resume
Stream drops with no completion result; re-send subscriptions/listen, then re-list
the user's token is valid
Still valid: issued by the authorization server
Still valid: issued by the authorization server
In practice: a gateway
The gateway pattern
Hosts connect to one MCP endpoint, the gateway. It fans out to many servers behind it.
It health-checks them. When one fails, the gateway hides its tools.
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
New servers are configured, never discovered at run time. Live tool changes flow through subscriptions/listen and list_changed.
The model sees changes on its next turn; an outage gives tool errors, not a lost conversation.
Modern restarts cost nothing but a re-listen and re-list; legacy restarts mean 404 and a new initialize.
Gateways make hot-swap practical; explicit handles like basket_id make state survive restarts.