If an MCP host connects to several servers, where does it keep the negotiated capabilities?
The safest answer is: inside the state for that particular server session.
The MCP architecture specification describes a stateful client–server session. Initialization negotiates a protocol version and capabilities for the participants in that session. That is a protocol fact; it does not prescribe whether an implementation uses a map, an object, or another storage structure.
The implementation consequence is an engineering inference: model each connection as its own state boundary. A single global capability registry can let one connection’s negotiated facts influence another connection’s behavior. For example, a host might see that server A exposed an optional feature and accidentally enable the same code path for server B, even though B never negotiated it.
That boundary should include more than a boolean capability set. Keep the negotiated protocol version, server identity, initialization status, and any connection-specific lifecycle state together. Pass that session object to capability-dependent operations rather than asking a process-wide registry whether an operation is “supported.”
Failure handling should be local too. If initialization fails, is missing, or the connection is replaced, mark only that session unavailable and stop capability-dependent calls through it until a fresh initialization completes. Do not repair the failure by borrowing another server’s negotiated state. The specification does not define your retry or telemetry policy, so those remain host design choices.
A small test matrix can expose accidental sharing:
session A: initialize with version/capabilities A
session B: initialize with version/capabilities B
replace A: close A, create A2, initialize with capabilities A2
assert: A, B, and A2 never authorize one another's optional operations
assert: an uninitialized or failed session cannot make capability-dependent calls
Also test the awkward transitions: B initializes while A is still negotiating; A reconnects after a transport failure; and a server advertises fewer capabilities on A2 than on A. The useful assertion is not merely that each handshake succeeds. It is that every decision about protocol version or optional behavior can be traced back to the session that negotiated it.
The MCP source establishes the session boundary. Keeping the resulting state isolated, and stopping cleanly when that state is invalid, is the host-side design discipline that follows from it.
Which session transition is hardest to represent cleanly in your host: reconnect, replacement, or partial initialization?