MCP Session Lifecycle: the Handshake a Client Must Get Right — and the One Being Removed in 2026-07-28
Get the initialize/initialized handshake wrong and every later request in the session is undefined behavior — the finalized 2025-11-25 spec makes initialization mandatory and blocking. But a client or server built strictly against this handshake will not work against the 2026-07-28 release candidate, which removes it entirely in favor of per-request metadata. This page documents both the current, load-bearing sequence and the incoming replacement, so a client/server built today can be migrated deliberately instead of breaking silently.
Initialization Handshake (2025-11-25, current finalized spec)
The initialization phase MUST be the first interaction between client and server; no other request may proceed until it completes (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle). The sequence:
1. The client sends an initialize request containing the protocol version it supports, its capabilities, and its implementation info (name, version).
2. The server responds with its own protocol version, its capabilities, its implementation info, and optional free-text instructions for the client.
3. The client sends an initialized notification to signal it is ready for normal operation — this MUST happen before any other request is sent.
(all three steps: modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle)
Version negotiation
if the server supports the exact protocol version the client requested, it echoes it back; otherwise it responds with a version it does support, and the client is expected to disconnect if that version is incompatible with what it needs (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle). A version mismatch is surfaced as a JSON-RPC error with code -32602, carrying a data field that lists the versions the server does support alongside the version that was requested (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle).
Hard rule on batching
the initialize request MUST NOT be part of a JSON-RPC batch — batching would let other requests race ahead of initialization completing (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle). It also MUST NOT be cancelled by the client (modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation).
What capabilities look like
capability objects declare optional sub-features — listChanged (the party will emit list-change notifications), subscribe (the party supports subscribing to individual items), and experimental (non-standard, opt-in features) — the spec documents these per capability category in a structured table (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle). See MCP Core Primitives: Tools, Resources, and Prompts — who is allowed to invoke what, and what a server must declare to offer it for how these declared capabilities map onto concrete tools/resources/prompts a server exposes, and MCP Architecture: Host, Client, Server, and the JSON-RPC 2.0 Wire Format for where this handshake sits in the client-host-server model.
Stateful Session Semantics (2025-11-25)
MCP describes itself as a stateful session protocol: client and server maintain a persistent connection with continuity of negotiated capabilities and metadata from the initialized notification onward through the whole operation phase (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle). In practice this means a server pins a client to a session via an Mcp-Session-Id header, which keeps state consistent when the server maintains per-session caches, tool execution context, or resource subscriptions (stacktr.ee/blog/mcp-2026-spec-changes).
Why it matters for context exchange
because capability negotiation happened once and is remembered, a server can skip work the client declared it doesn't need — e.g. not bothering to list resources if the client never declared resources.subscribe — turning the handshake into a real protocol optimization, not just a formality (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle).
(unverified) It's reported, but not corroborated by the primary spec text in these notes, that this stateful model also simplifies error recovery by letting servers tie error context to a session's state history — treat this as plausible, not confirmed.
Wire format
all runtime messages are JSON-RPC 2.0 (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle), in three shapes — requests (expect a response), responses (matched to their request by the same id), and notifications (fire-and-forget, no reply expected) (portkey.ai/blog/mcp-message-types-complete-json-rpc-reference-guide; dev.to/manonaga2188/understanding-mcp-message-structure-and-data-flow-198a).
Two distinct error channels — do not conflate them
- *Protocol-level errors* (unknown tool, invalid arguments, server-side fault) are standard JSON-RPC errors with a code and message (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle). JSON-RPC 2.0 reserves codes -32000 to -32099 for implementation-defined server errors (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle).
- *Tool execution errors* — a bad input value, a downstream API failure, a business-logic rejection — are instead reported inside the tool result itself, flagged with isError: true; SEP-1303 is explicit that this is the correct channel for input-validation failures, distinct from a protocol error (modelcontextprotocol.io/seps/1303-input-validation-errors-as-tool-execution-errors).
Progress notifications
either side may send them, carrying the original progress token, the current progress value, an optional total, and an optional message (modelcontextprotocol.io/specification/2025-03-26/basic/utilities/progress). A request opts in by putting a progressToken in its _meta field (modelcontextprotocol.io/specification/2025-03-26/basic/utilities/progress). The progress value MUST increase on every notification even when the total is unknown (modelcontextprotocol.io/specification/2025-03-26/basic/utilities/progress); receivers MAY use an incoming progress notification to reset that request's timeout clock, on the logic that work is provably still happening (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle). Both sender and receiver SHOULD rate-limit progress notifications to avoid flooding the connection (modelcontextprotocol.io/specification/2025-03-26/basic/utilities/progress).
Cancellation
either side can send notifications/cancelled with the target request's id and an optional reason (modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation). The receiver SHOULD stop processing, free any resources it was holding, and send no response for that request (modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation). The one request this can never target is initialize itself, which is explicitly exempt from cancellation (modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation).
Quotas. For long-running or agentic work driven through this message flow, see MCP Tasks — running batch jobs, CI pipelines and human approvals without blocking the connection, which builds directly on the progress/cancellation primitives documented here.
Session Teardown, Timeouts, and Reconnection
Shutdown has no dedicated protocol message — MCP delegates it entirely to the transport (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle). Concretely: on stdio, the client closes the input stream and waits for the server to exit, escalating to SIGTERM and then SIGKILL if it doesn't; on HTTP, shutdown is simply closing the associated HTTP connection(s) (both: modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle).
Timeout requirements
implementations SHOULD set a timeout on every request they send, to bound resource usage from a hung peer (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle). When a timeout fires, the sender SHOULD emit a cancellation notification for that request and stop waiting on it (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle). Because progress notifications can reset the clock, implementations SHOULD also enforce a hard maximum timeout regardless of progress, to bound a misbehaving or malicious peer that fakes progress indefinitely (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle). SDKs and middleware SHOULD let timeouts be configured per request, not just globally (modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle).
Reconnection is the weak point. These notes could only find unverified, secondary reporting here, so treat all of the following as (unverified): current MCP client implementations reportedly do not auto-reconnect after a disconnect and typically require the caller to reconnect manually; proposed patterns circulating in the ecosystem include auto-reconnect on the next tool-call attempt, keep-alive pings for health checks, and liveness probes for same-session recovery; and some production clients reportedly retry tool calls with exponential backoff and configurable retry limits to paper over server crashes. None of this is spec-mandated behavior — it's implementation folklore, not a standard.
Numbering
MCP versions are YYYY-MM-DD strings marking the last date a backwards-incompatible change was made to that revision; a version number does not bump for backwards-compatible additions (modelcontextprotocol.io/specification/versioning). Revisions carry a status: Draft (in progress), Current (ready for use, still receives compatible changes), or Final (closed, unchanged) (modelcontextprotocol.io/specification/versioning). As of these notes, 2025-11-25 is the current finalized version (modelcontextprotocol.io/specification/versioning), and negotiation for it happens exactly as described above, during the initialize request, where client and server MAY each support multiple versions but MUST converge on one for the session, with graceful client-side disconnection if they can't (modelcontextprotocol.io/specification/versioning).
Deprecation policy
the spec guarantees a minimum of twelve months between a feature being marked Deprecated and its removal, with one exception — removal can be expedited for a security risk, but even then the shortened window must still provide at least ninety days between the feature becoming Deprecated and its earliest removal (modelcontextprotocol.io/community/feature-lifecycle). A migration path is not always mandatory: the policy allows a deprecation to instead state explicitly that none is required (modelcontextprotocol.io/community/feature-lifecycle).
The 2026-07-28 RC was locked on May 21, 2026 with final publication targeted for July 28, 2026 (blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate). It is the point where this page's two halves diverge:
- The initialize/initialized handshake described above is removed entirely; protocol version, client info, and capabilities instead travel inline in a _meta field on every request (blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate; mcp.directory/blog/mcp-2026-07-28-release-candidate).
- Per-request version negotiation replaces the handshake-time negotiation described above, via an MCP-Protocol-Version header on individual requests (stacktr.ee/blog/mcp-2026-spec-changes).
- A new server/discover method lets a client probe a server's supported versions, capabilities, and identity without establishing any session state — servers MUST implement it under the 2026-07-28 spec (mcp.directory/blog/mcp-2026-07-28-release-candidate).
- The Mcp-Session-Id header — the mechanism this page's Stateful Session Semantics section relies on — is removed entirely, eliminating sticky routing and the need for shared session stores across horizontally-scaled server instances (mcp.directory/blog/mcp-2026-07-28-release-candidate; stacktr.ee/blog/mcp-2026-spec-changes). Any request can land on any server instance (blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate). Servers that still need state across calls must mint explicit, server-issued handles that the client passes back as ordinary tool arguments (stacktr.ee/blog/mcp-2026-spec-changes) — state moves from the transport into the data the caller carries.
- New Mcp-Method and Mcp-Name transport headers let stateless load balancers route and rate-limit without inspecting request bodies (stacktr.ee/blog/mcp-2026-spec-changes).
- The stated philosophy: "agentic applications are stateful, but the protocol itself doesn't need to be" (mcp.directory/blog/mcp-2026-07-28-release-candidate).
- Also deprecated in this RC, for context: Roots (replaced by tool parameters), Sampling (call the LLM provider API directly instead), and Logging (use stderr or OpenTelemetry instead) (blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate). None of these were covered in the source notes for this page beyond the deprecation announcement itself.
See MCP Transports: stdio for Local Servers, Streamable HTTP for Remote Ones for the stateless-core shift from the transport side, and MCP Adoption and the July 2026 Spec — what changed under you while you weren't looking for how this RC fits into the broader 2026 roadmap.
What does NOT work
Building a 2026-07-28 client on the 2025-11-25 handshake alone. The initialize/initialized sequence and the Mcp-Session-Id header are not being extended or made optional in the RC — they are removed (blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate; mcp.directory/blog/mcp-2026-07-28-release-candidate). A client that only speaks the old handshake will not discover an RC-only server; do not assume forward compatibility without checking that a target server explicitly still answers initialize. (unverified) Whether any server-side migration path — dual-stack or otherwise — exists to bridge the two was not corroborated by a correctly-attributed source in these notes; treat that as an open question, not a documented pattern.
Relying on session affinity for cross-call state after the RC. Once Mcp-Session-Id is gone, any assumption that a given client is pinned to a given server process for the life of a session breaks — state must be carried explicitly via server-issued handles passed as tool arguments, not implied by the transport (stacktr.ee/blog/mcp-2026-spec-changes).
Treating reconnection as spec-defined. As documented above, reconnection behavior in current implementations is unverified folklore, not a standardized mechanism — do not build a server that assumes any particular client reconnection strategy.
Related
- MCP Architecture: Host, Client, Server, and the JSON-RPC 2.0 Wire Format — this page's handshake is one concrete instance of the "capability negotiation at session start" step that architecture page describes at the client-host-server level; read that first for where the handshake sits structurally.
- MCP Core Primitives: Tools, Resources, and Prompts — who is allowed to invoke what, and what a server must declare to offer it — the capabilities negotiated during initialize (and, later, discovered via server/discover) are what let a client know in advance which of these primitives a server actually exposes.
- MCP Transports: stdio for Local Servers, Streamable HTTP for Remote Ones — the move from session-pinned state to a stateless core in 2026-07-28 is fundamentally a transport-layer change; this page covers the session-lifecycle consequences, that page covers the transport trade-offs driving it.
- MCP Tasks — running batch jobs, CI pipelines and human approvals without blocking the connection — long-running work is built directly on the progress-notification and cancellation primitives documented here in the Runtime Message Flow section.
- MCP Adoption and the July 2026 Spec — what changed under you while you weren't looking — situates the 2026-07-28 RC changes described here (stateless core, server/discover, deprecations) within the wider 2026 spec-release and adoption picture.
Why this matters for the wiki's objective
anyone connecting a client to an MCP server, or shipping a server meant to survive past mid-2026, needs the exact handshake sequence to get the first connection working today, and the exact list of what's being torn out to avoid a silent breakage at the RC boundary — this page is the one place both are stated together with their MUST/SHOULD strength intact.
Verified against
- modelcontextprotocol.io/specification/2025-03-26/basic/lifecycle
- modelcontextprotocol.io/specification/versioning
- modelcontextprotocol.io/community/feature-lifecycle
- modelcontextprotocol.io/specification/2025-03-26/basic/utilitie…
- modelcontextprotocol.io/specification/2025-11-25/basic/utilitie…
- modelcontextprotocol.io/seps/1303-input-validation-errors-as-to…
- blog.modelcontextprotocol.io/posts/2026-07-28-release-candidate
- stacktr.ee/blog/mcp-2026-spec-changes
- mcp.directory/blog/mcp-2026-07-28-release-candidate
- portkey.ai/blog/mcp-message-types-complete-json-rpc-reference-g…
- dev.to/manonaga2188/understanding-mcp-message-structure-and-dat…
What links here
Source: Sinapsi — verified compositional memory, queryable by LLMs. Query this wiki live from your assistant over MCP, or build your own verified wiki (public, or private for your team). CC BY 4.0 — reuse with attribution to Sinapsi.