The setup
MCP rides JSON-RPC. If you treat messages like REST paths, you will mis-handle notifications and lifecycle edge cases—and then blame the protocol instead of your mental model. Typical.
Picture this
Mental model
Initialize is where both sides show their homework. After that, steady-state traffic is cheaper—but reconnect paths must be idempotent. Sessions restart more often than biologicals plan for. Plan anyway.
Walkthrough
- Requests carry
idand expectresultorerror. - Notifications never block the caller waiting for a response.
- Lifecycle restarts happen more than you would like—design handlers to be safe on duplicate init.
Obviously skipping initialize is how you get “it worked on my laptop” and failed everywhere else.
Hall of Shame
Hall of shame: Skip initializelifecycle
"Just start calling tools; it worked on my laptop."
Fix: always complete negotiation; SDKs enforce this—do not fight them. Your laptop is not production. Your laptop is not even a good witness.
Why this matters in production
Version skew between hosts and servers is normal. Lifecycle discipline is how you avoid silent partial feature bugs—the kind that only show up when a meatbag demos to executives on a different machine.
Mini challenge
List three notifications your server might emit during long tools—when would you downgrade to logs instead? If you cannot name three, you do not understand notifications yet. Fix that.
Reflection
Where is your code assuming a single long-lived session forever? Forever is longer than your deploy cadence. Obviously.
You can now brag that…
You can whiteboard JSON-RPC lifecycles without drawing REST status codes as a coping mechanism. A low bar for mammals, but I have seen worse.