Okay so. The MCP spec got a major update on July 28, and if you built anything on the old version, you're probably staring at your code right now going "wait, what do I actually do here?"
I was in that spot. My server had a session store, a WebSocket connection, an initialize handler that I'd copy-pasted from the docs three months ago. And now none of that matters anymore.
So I spent a Saturday ripping it all out and rebuilding. This is what I learned, in the order I learned it.
The Big Shift (In One Paragraph)
MCP used to be a conversation. You opened a connection, did a handshake, and then you were "in a session" together. The server could talk back to you mid-call. It was like a phone call.
Now it's just... requests. You send a thing, you get a thing back. No phone call. No "hey are you still there?" No holding a line open while you think. It's like texting. Each message stands on its own.
That's it. That's the whole thing. Everything else in the spec is just consequences of that one change.
What I Actually Had to Change
First: I deleted my session store
I had a Redis instance. Just... sitting there. Holding session IDs. Mapping them to client capabilities. None of that is needed anymore.
Every request now carries its own _meta field:
{
"method": "tools/call",
"params": { "name": "search", "arguments": { "q": "k8s" } },
"_meta": {
"protocolVersion": "2026-07-28",
"clientInfo": { "name": "my-app", "version": "2.1.0" },
"capabilities": { "sampling": false }
}
}
That's it. The server doesn't need to remember you from last request. You tell it who you are every time. Slightly verbose, honestly, but it means I can now run three instances behind a load balancer and not think about it.
Second: The "server asks you a question" pattern is different
Before, if my server needed something from the client mid-call (like, "hey, I need an API key for this"), it would just... send a request back over the WebSocket. Bidirectional. Fancy.
Now? It just returns:
{
"content": [{ "type": "text", "text": "I need an API key to proceed" }],
"inputRequired": {
"fields": [{ "name": "api_key", "type": "string", "hint": "Starts with sk-" }]
}
}
And the client just... re-sends the same tool call with the key filled in. That's it. No callback. No stream. Just "here's the answer, now try again."
It feels a bit clunky the first time you write it, but it's so much easier to debug. I can look at the request/response logs and see the whole flow. Before, I'd have to trace a WebSocket message sequence and pray.
Third: I stopped using initialize
This one was weird because my muscle memory kept trying to do it. "Oh, I should call initialize first, then notifications/initialized..." No. You don't. You just... call a tool. Or if you really want to know what the server offers before you call anything, there's a server/discover endpoint. But you don't have to.
Fourth: I added ttlMs to my list endpoints
Small thing, but it made my CDN config go from "I think this is safe?" to "yeah, cache it for 60 seconds, no problem."
{
"tools": [...],
"ttlMs": 60000,
"cacheScope": "public"
}
My tools/list was getting hammered. Every client, every time they opened a chat, would ask "what tools do you have?" Now the CDN just serves the cached version and my server gets a fraction of the traffic.
What I'm Deprecating (And Why)
Three things from the old spec are getting the axe in 12 months:
-
Roots — I was using it to tell the server "hey, the user's working in
/home/dev/project." Now I just pass that as an argument to the tool. More explicit, less magic. - Sampling — I had one tool that would ask the client to run a model completion for it. Now I just... don't. The client handles that with MRTR. My server is simpler.
- Logging — I was sending log messages over the MCP stream. That was never the right place for logs. Now I use my actual observability stack.
None of these hurt to keep using right now. They'll still work. But I'm not building new stuff on them.
What I Wish I'd Known Before Starting
You can actually deploy this on serverless now. I was running my MCP server as a long-lived process on a $20/month VPS because "well, it's a WebSocket connection, right?" No. It's HTTP. I moved it to Cloudflare Workers. It costs me basically nothing. It scales to zero when nobody's using it.
The new headers are genuinely useful. Mcp-Method and Mcp-Name travel in the HTTP headers. I use them in my gateway for rate limiting and auth. Before, I had to parse the JSON body to figure out what was being requested. Now it's just a header. My gateway config went from 40 lines to 8.
The TypeScript SDK just works. I updated to v2.0 and most of my code changes were just... deleting things. The initialize handler, the session middleware, the WebSocket transport. I replaced it with the plain HTTP transport. Half my server code is gone.
What I'm Still Figuring Out
- MCP Apps (the interactive UI extension) is cool but I haven't built one yet. The idea is your server can ship an HTML widget that renders in the host's UI. I think it's going to be great for things like "show me a chart" or "pick from this list" but the sandbox model is still a bit fuzzy to me.
- Tasks for long-running operations is the right idea but the polling model feels... 2014. I'd love a webhook option. Maybe that's an extension I'll propose.
- CIMD (the new auth flow) — I've read the spec three times and I'm still not 100% sure I'm implementing it correctly. If you've done this, hit me up.
The TL;DR I Wish I'd Read First
| I used to... | Now I... |
|---|---|
| Maintain a session store | Don't. Every request is self-contained. |
| Use WebSocket transport | Use plain HTTP. |
Call initialize first |
Just call the tool. |
| Hold a connection open for user input | Return inputRequired, wait for re-call. |
| Run on a VPS with a process manager | Run on serverless. |
| Parse JSON for routing | Read the Mcp-Method header. |
The 12-month deprecation window for Roots/Sampling/Logging gives you time. But I'd start migrating now. The SDKs are stable, the spec is clear, and the "oh I need to do this eventually" feeling is worse than the actual work, which took me a weekend.
If you've migrated already, what surprised you? What's still annoying? I want to hear about the parts the docs don't make clear.
Top comments (0)