MCP in Practice

Lesson 5 of 36

How MCP got here: three eras in twenty months

Twenty months, and the current version removed more than it added

MCP was published in November 2024. In the twenty months since, it has been revised repeatedly, and the current revision — 2026-07-28 — is unusual in that its headline changes are deletions. Sampling, roots and protocol-level logging were all deprecated. The connection handshake was made optional. The protocol got smaller.

This lesson exists for two reasons. The first is that each deletion has a nameable cause, and knowing the causes tells you a great deal about what MCP is for. The second is more practical: most MCP material on the internet was written before these changes, so a reader coming to this course from a blog post or a video is carrying assumptions that are now wrong. Rather than let you discover that one confusing error message at a time, here is the whole list.

The standing appointment and the ticket window

Era one worked like a standing appointment. A client connected, introduced itself, agreed capabilities with the server, and then started making requests. The connection carried that agreement — the server remembered who you were and what you had negotiated, and everything after the handshake relied on that memory.

Era three works like a ticket window. Every request carries its own paperwork: the protocol version, what the client supports, who the client is. Nothing is remembered between requests, so any teller can serve any customer.

A beginner gets from this that the introduction stopped being a separate step. An experienced engineer gets something more specific and immediately actionable: session affinity is gone, which means a remote MCP server can sit behind an ordinary load balancer with no sticky routing, scale horizontally, and survive a process restart mid-conversation. That is the entire reason the change was made, and it is why the deletions are not losses.

The session arrived with stdio, strained under HTTP, and was removed in 2026-07-28
The session arrived with stdio, strained under HTTP, and was removed in 2026-07-28

Era one: the session (November 2024)

The original protocol opened with an initialize request. The client announced its version and capabilities, the server replied with its own, and the client sent initialized to confirm. Only then could work begin.

This was a natural design, because the original transport was stdio: a subprocess on your machine, one client, a connection that lives exactly as long as the process. State on that connection costs nothing.

Era two: HTTP, and what sessions cost

Then people wanted remote servers, and HTTP arrived as a transport. The session model came with it, carried by a session id header — and started to hurt. A stateful session means a given client must keep reaching the same process. That is sticky routing, which constrains load balancing; it means a deploy drops live sessions; it means a server must hold per-connection memory that scales with concurrent users; and it means reconnection logic on every client.

None of this is fatal, and plenty of protocols live with it. But MCP's actual workload is overwhelmingly stateless — "list your tools", "call this tool with these arguments". The session was carrying almost nothing, at considerable cost.

Era three: stateless (2026-07-28)

So the session was removed. MCP is now a stateless protocol. Every request carries what the server needs to process it, in a _meta field:

json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/list",
  "params": {
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientInfo": {"name": "example-client", "version": "1.0.0"},
      "io.modelcontextprotocol/clientCapabilities": {"elicitation": {}}
    }
  }
}

initialize is replaced by server/discover, which every server must implement and no client is required to call. It is a convenience: one request that returns the server's identity, capabilities and supported versions, cacheable, so a client that wants to know up front can ask. A client that does not care can call tools/call immediately.

If a server does not support the version a request declares, it returns UnsupportedProtocolVersionError (-32022) listing what it does support, and the client retries. Version negotiation became an error path instead of a handshake.

The four things that went away

If you have read an older tutorial, it may well be built on one of these. Each was real and each was right for the protocol as it stood; each has a replacement.

  • Sampling let a server ask the client to run an LLM completion on its behalf, so a server could use a model without holding an API key. It is deprecated; integrate an LLM provider's API directly in your server instead. The trade is honest: you now own the key, the cost and the model choice, which is more work. It was made because sampling required a server-to-client request in the middle of handling a client request — precisely the shape a stateless protocol cannot support — and because in practice very few clients implemented it, so servers that depended on it worked almost nowhere.
  • Roots let a client tell a server which directories it should operate in. It is deprecated; pass paths as ordinary tool arguments and validate them. Roots were always advisory — the spec said servers should respect them, never must, because a server runs code the client cannot control — so they were never a security boundary, and a mechanism that looks like a boundary but is not is worse than no mechanism.
  • Protocol logging (notifications/message) let a server send log records to the client over the protocol. It is deprecated; log to stderr, which the host already captures, or use OpenTelemetry. It required the server to push messages to the client unprompted, which again is the shape that had to go.
  • The initialize handshake is replaced by the optional server/discover, as above.

The pattern is worth naming: everything removed required the server to speak first. A stateless protocol has no open channel for a server to speak into, so each of those mechanisms either had to be redesigned or removed. Elicitation — the one case where a server genuinely must ask the user something — was redesigned rather than removed, and Lesson 22 is about how.

Everything deprecated in 2026-07-28 required the server to speak first, and each has a replacement
Everything deprecated in 2026-07-28 required the server to speak first, and each has a replacement

What to take into the next lesson

MCP dropped its session in favour of per-request _meta, which bought horizontal scaling and cost it every mechanism where the server spoke first: sampling, roots and protocol logging are deprecated, and initialize became the optional server/discover. Next: the wire itself — one request and one response, field by field, so the rest of the course is new values in slots you already recognise.

← Previous