SKIP TO CONTENT
AI AGENTS6 minAgents in Production / Ep. 11 · Stage 11-4

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?

XIN
Pixel-art night scene of a row of identical server cabinets with no luggage beside them. Each glowing envelope arriving on a conveyor carries its own small backpack, and a teal lockbox key hangs from one envelope.

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

Flow diagram: 7 stepsFlow diagram: 7 steps. A request arrives, then Headers match the body?. Headers match the body?, then Required facts present, version supported?. Required facts present, version supported?, then Who is calling?. Who is calling?, then Capabilities cover the work?. Capabilities cover the work?, then Sealed state and handles OK?. Sealed state and handles OK?, then Run, then write one log row.1A request arrives2Headers match the body?3Required facts present,version supported?4Who is calling?5Capabilities cover thework?6Sealed state and handlesOK?7Run, then write one logrow
The spec names this error HeaderMismatch. Missing protocolVersion or clientCapabilities gives -32602. An unsupported version gives UnsupportedProtocolVersionError, -32022. The spec names this error MissingRequiredClientCapabilityError.

Step 1. Arrive. A request comes in, on any instance.

Pick a step to see it move
  1. Arrive A request comes in, on any instance.
  2. Mismatch: refused The tool name in the header differs from the body: HTTP 400, error -32020. The row is still written.
  3. Required facts Missing version or capabilities: -32602, HTTP 400. An unsupported version: -32022, with the supported list.
  4. Caller The principal comes from the access token, never from clientInfo.
  5. Capabilities A capability the work needs wasn’t declared: -32021.
  6. Sealed state, handles Check the seal, caller, expiry and request (that limits replay, not single use), and who owns each handle.
  7. Run and log Success or failure, one row is written.
Error codes and HTTP statuses are from the spec. The order and the single log write at the end are my proposal.

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.

SIX STEPS TO THE CUTOVER
1

List every per-session field

Search for the session id, initialize and anything keyed by connection or process.

2

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.

3

Add discovery and per-request facts

Implement server/discover, check headers against the body, put a resultType on every result.

4

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).

5

Move lasting state behind handles

Shared store, owner check, expiry, and an idempotency key on every tool that writes.

6

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.

My proposal, not part of the spec.

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

An old client fails against a server that only speaks 2026-07-28, so choose before the cutover.

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

Adam Boudjemaa
Adam Boudjemaa

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 role
Follow the engineering notes

Get new articles on production AI and smart contracts by email.