One Server, One Session: The Isolation Rule Hiding in MCP

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?

The replacement case is the one I would make most explicit: a new connection should not merely receive a fresh capability set; it should receive a new session identity.

That distinction matters when a host is coordinating a visual or asset-producing step. A queued action may still hold a reference to the old server session after the transport has been replaced. If the host updates one shared capability object in place, that old action can look valid even though its negotiated context belongs to a connection that no longer exists.

A useful design is to give each connection a monotonically changing session generation and bind capability-dependent work to that generation. On replacement, invalidate the old generation before publishing the new initialized session. The dispatcher can then reject stale work and require it to be rebuilt against the new session, rather than silently carrying over tool or protocol assumptions. This is a host-side lifecycle pattern, not a claim that the MCP specification mandates a generation counter.

The test I would add to the article’s matrix is: queue an optional operation under A, replace A with A2 before dispatch, initialize A2 with a narrower capability set, and assert that the queued operation cannot run until it has been revalidated for A2. That catches the subtle bug where isolation exists in storage but not at the boundary where work is actually authorized.