MCP Migration: What to Log and Check Before You Cut Over
In my proposal, every request to a stateless MCP server gets its own log row. What goes in it, and what do you check before you switch?

IN 30 SECONDS
A per-request log row is my proposal for auditing a stateless MCP server: one row per request, success or failure. Then a six-step checklist moves each kind of session data out before the cutover.
KEY TAKEAWAYS · 3
- My proposal: one log row per request, with the method, tool name, protocol version, principal, trace context, handles and result type.
- clientInfo is self-reported, so log it as a claim, never as identity.
- A server that supports only the new version locks out old clients, which have no way to fall forward. Decide on serving both before you cut over.
On this page
What an MCP server should log on every request
IN SHORT My proposal: run the checks in a fixed order, and write one log row per request, whether it succeeds or fails.
Anything marked as a proposal is my opinion, not the standard. Checks on the caller assume an HTTP server that authenticates each call. The spec makes authorization optional.
WORDS TO KNOW
- Principal
- The user or service a request acts for, shown by its access token.
- clientInfo
- The name and version a client reports about itself. The protocol doesn’t verify it.
- Trace id
- An id carried with a request so its log lines in different systems can be joined.
- Sticky sessions
- A load balancer setting that keeps a client on the same server instance.
With no session, there’s no session log: each request must be rebuilt from its own row.
CHECKS ON EVERY REQUEST
Step 1. Arrive. A request comes in, on any instance.
- Arrive A request comes in, on any instance.
- Mismatch: refused The tool name in the header differs from the body: HTTP 400, error -32020. The row is still written.
- Required facts Missing version or capabilities: -32602, HTTP 400. An unsupported version: -32022, with the supported list.
- Caller The principal comes from the access token, never from clientInfo.
- Capabilities A capability the work needs wasn’t declared: -32021.
- Sealed state, handles Check the seal, caller, expiry and request (that limits replay, not single use), and who owns each handle.
- Run and log Success or failure, one row is written.
IN PLAIN WORDS
The row says what was called, on which version, by whom (the Principal The user or service a request acts for, shown by its access token. See the glossary from the token) and how it ended. clientInfo The name and version a client reports about itself. The protocol doesn’t verify it. See the glossary goes in as a claim, never as identity.
That’s the transport half of the audit trail. The decision half is the record from proving an agent’s work to an auditor. Put the same Trace id An id carried with a request so its log lines in different systems can be joined. See the glossary in both and join them on it.
The migration checklist I’d run
IN SHORT My proposal in six steps: list the state, decide about old clients, then move each kind of state out.
List every per-session field
Search for the session id, initialize and anything keyed by connection or process.
Decide: new version only, or both
A dual-era server MAY serve both on one endpoint. A modern-only one should list its versions in the error it sends back to initialize.
Add discovery and per-request facts
Implement server/discover, check headers against the body, put a resultType on every result.
Move mid-call state into sealed state
Swap the server’s elicitation and sampling requests for input_required. Bind the state to caller, expiry and request (that limits replay; single use needs a server-side check).
Move lasting state behind handles
Shared store, owner check, expiry, and an idempotency key on every tool that writes.
Ship the log row, then test
Turn off sticky sessions in staging and replay a multi-step workflow. If anything breaks, some state may still be hiding in a process.
I’d never skip step 6. Sticky sessions A load balancer setting that keeps a client on the same server instance. See the glossary can hide state that still lives in a process.
ITEM GET
Decide on old clients first
The MCP roadmap (last updated 22 August 2026) reflects “current thinking rather than firm commitments.” The Transports working group wants Streamable HTTP to be the only binding, even for local servers.
A new Agent Identity group, still forming, lists priorities that include DPoP, workload identity federation and RFC 8693 token exchange. Its reason: “Increasingly the caller is an agent.”
Sources: changelog, versioning, Streamable HTTP, roadmap, all read on 7 October 2026.
FAQ
No. The spec’s compatibility matrix lists that pairing as failing, and old clients can’t fall forward. A server may support both eras: initialize picks the old rules, and a request with the modern _meta is served statelessly.
No. Version 2026-07-28 deprecates it: it still works through a window of at least 12 months, and the changelog suggests stderr on stdio or OpenTelemetry instead. Write your own row per request, whether or not the client asked for a log level.
STAGE CLEAR
What you now know
- Why, in my proposal, each request gets its own log row, and why clientInfo goes in as a claim.
- The six migration steps, and what to decide about old clients first.
Check yourself
Tap or click a question to see the answer.
Your staging test passes with sticky sessions on. Are you ready to cut over?
Not yet. Sticky sessions keep a client on one instance, so state hidden in a process still works. Turn them off and replay a multi-step workflow first.
You finished every stage published so far in this series. Back to the series map
Former CTO of Integra. Named author (1 of 5) of ERC-3643, first author of ERC-6960, co-author of ERC-7410, and co-author of ERC-8203, which is still a draft. Building production AI and regulated Web3 systems.
Have a project to ship or a role to fill?
Discuss your project or roleFollow the engineering notes
Get new articles on production AI and smart contracts by email.