On this page
- Start with the modern request shape
- The protocol no longer needs a session handshake
- Why statelessness matters operationally
- Required headers make requests routable
- Header/body disagreement is an error
- Build headers from the normalized request
- Mcp-Param-* can mirror selected parameters
- HTTP status and JSON-RPC errors are different layers
- Authentication is still ordinary HTTP authorization
- A stateless request is easier to retry—but not automatically safe
- Distinguish no-response from explicit failure
- Do not hide retries below the tool coordinator
- Streaming responses are not the same as subscriptions
- subscriptions/listen is deliberate opt-in state
- Subscription streams are reconnectable, not authoritative state
- Graceful close and unexpected close differ
- Modern interactive flows use MRTR instead of server-initiated request channels
- Long-running work should not hold one HTTP request forever
- Request identifiers are not business idempotency keys
- Protocol-version handling must be explicit
- Migrating from legacy HTTP+SSE
- Request cancellation is best effort
- Timeouts should be layered
- Proxy behavior matters
- TLS is part of server identity
- Redirects require care
- Do not log sensitive headers
- Trace operation stages separately
- Header mismatch tests are essential
- Test load-balanced statelessness
- Test unknown outcomes
- A clean transport boundary
- Where BYOKchat fits
- Further reading
Streamable HTTP is the primary network transport shape for modern remote MCP.
The important word in 2026 is no longer “streamable.” It is stateless.
The 2026-07-28 MCP revision removes the modern protocol’s old initialization/session assumptions and makes each request independently routable.
That changes how clients should think about:
- connection lifecycle;
- load balancing;
- retries;
- server identity;
- subscriptions;
- interactive requests;
- long-running work.
Start with the modern request shape
A modern MCP request is an HTTP POST containing a JSON-RPC message plus protocol-routing headers.
Conceptually:
POST /mcp HTTP/1.1
Content-Type: application/json
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Authorization: Bearer <token>
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "search",
"arguments": {
"q": "stateless MCP"
},
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28"
}
}
}
The exact metadata depends on the request and implementation, but the architecture is clear:
HTTP request
+ MCP protocol version
+ routable operation metadata
+ JSON-RPC body
The protocol no longer needs a session handshake
Older MCP clients often used this lifecycle:
initialize
-> initialized
-> receive/use Mcp-Session-Id
-> send subsequent requests in that session
For 2026-07-28 modern requests, that protocol-level handshake is removed.
So the modern mental model becomes:
configured endpoint
-> optionally discover capabilities
-> send independent operation
-> receive result
That does not mean the application cannot keep durable state.
It means the transport no longer hides that state in an MCP session.
Why statelessness matters operationally
A request can land on any healthy server instance.
No sticky routing is required by the modern protocol core.
This enables ordinary web infrastructure:
- round-robin load balancing;
- autoscaling;
- serverless request handling;
- per-request tracing;
- simpler failover.
The application can still use databases or explicit handles for business state.
Required headers make requests routable
The 2026-07-28 protocol generation requires routing headers on modern Streamable HTTP requests.
Important examples include:
MCP-Protocol-Version
Mcp-Method
Mcp-Name
Mcp-Name applies when the operation has a meaningful name/URI/task identifier that the HTTP binding mirrors.
The goal is that gateways can inspect the operation without parsing the JSON body.
This supports:
- routing;
- rate limiting;
- observability;
- policy enforcement;
- diagnostics.
Header/body disagreement is an error
A client must not send:
Mcp-Method: tools/call
while the JSON-RPC body says:
{
"method": "resources/read"
}
Likewise, if a mirrored name/header disagrees with the body, a compliant modern server can reject the request.
Treat header generation as part of request serialization, not as decorative metadata.
Build headers from the normalized request
A safe implementation order is:
construct normalized MCP request
-> validate operation
-> derive required headers from normalized request
-> serialize body
-> send both together
Do not let UI code or caller-supplied arbitrary header strings define Mcp-Method independently.
One request object should be the source of truth.
Mcp-Param-* can mirror selected parameters
The modern HTTP binding also supports mirrored request parameters for schemas that designate them with the relevant MCP header annotation/extension.
That enables infrastructure to route or inspect selected operation parameters without reading the body.
The security implication is important:
Any value mirrored into an HTTP header may be exposed to infrastructure logs and intermediaries that treat headers differently from bodies.
Do not casually mark secrets or highly sensitive free-form values for header mirroring.
HTTP status and JSON-RPC errors are different layers
An MCP client should preserve both.
Examples:
TCP/TLS failure
HTTP 401
HTTP 429
HTTP 500
JSON-RPC error
successful JSON-RPC result containing tool-level failure
Those are not interchangeable.
A useful internal error model might include:
interface MCPFailure {
transport?: TransportError
httpStatus?: number
rpcCode?: number
rpcMessage?: string
operation?: string
retryClass: RetryClass
}
This makes retry and UI behavior more precise.
Authentication is still ordinary HTTP authorization
Remote MCP servers can use OAuth bearer tokens or other supported HTTP authentication patterns.
The transport layer should attach credentials through secure headers.
Do not embed tokens in:
query strings
server display names
conversation history
JSON tool arguments
Credential storage and refresh should remain outside the conversation/tool record.
See OAuth for MCP Explained and How to Secure Remote MCP Servers.
A stateless request is easier to retry—but not automatically safe
Transport statelessness removes one source of retry complexity.
It does not make side-effecting operations idempotent.
Suppose:
POST tools/call create_invoice
The server processes it, but the network fails before the client receives the response.
The client sees:
unknown outcome
Retrying blindly may create a second invoice.
The correct rule remains:
Retry only when operation semantics and idempotency guarantees permit it.
See Idempotency for AI Tool Execution.
Distinguish no-response from explicit failure
These cases are different:
server returned validation error
versus:
connection dropped after request bytes were sent
In the second case, the side effect may have happened.
Your retry engine should represent an outcomeUnknown state.
Do not hide retries below the tool coordinator
A generic HTTP library configured for automatic retries can be dangerous.
For example:
network library retries POST on connection reset
without knowing the operation was destructive.
Prefer retry policy at a layer that knows:
- MCP method;
- tool identity;
- side-effect class;
- idempotency key/support;
- current approval state.
Streaming responses are not the same as subscriptions
The word “streamable” can create confusion.
There are at least three different streaming concepts:
- a response delivered incrementally while one operation is active;
- a long-lived
subscriptions/listenstream for selected notifications; - model-provider token streaming, which is outside MCP entirely.
Keep them separate in code.
MCP operation response stream != MCP subscription != model token stream
subscriptions/listen is deliberate opt-in state
Modern MCP does not rely on an always-open session notification channel.
A client can explicitly call subscriptions/listen with the notification categories it wants.
Conceptually:
{
"jsonrpc": "2.0",
"id": "listen-1",
"method": "subscriptions/listen",
"params": {
"notifications": {
"toolsListChanged": true,
"resourcesListChanged": true
}
}
}
The server acknowledges the supported subset, then publishes matching notifications on the stream.
Subscription streams are reconnectable, not authoritative state
A subscription can fail because:
- mobile app suspended;
- network changed;
- proxy timeout;
- server deployment;
- process crash;
- authentication expired.
After reconnect, do not assume every notification was observed.
Reconcile authoritative state when needed.
For example:
tools-list-changed notification
-> refresh tools/list
rather than trying to mutate the list from a missed delta stream.
Graceful close and unexpected close differ
A client should know whether the server deliberately completed a subscription or the connection simply disappeared.
The implementation can expose something like:
graceful
remoteDisconnect
cancelledByClient
authExpired
The reconnection policy can then be appropriate.
Modern interactive flows use MRTR instead of server-initiated request channels
The stateless core cannot depend on a server keeping a client-specific request channel alive so it can ask questions later.
For operations that need more input, modern MCP uses Multi Round-Trip Requests.
Conceptually:
client sends tools/call
server returns input_required
client collects user/model input
client retries original call with inputResponses + requestState
This preserves stateless HTTP routing.
See MCP Multi-Round-Trip Requests Explained.
Long-running work should not hold one HTTP request forever
A five-minute deployment or export is better modeled as a task than as one fragile request.
With the Tasks extension:
operation
-> task handle
-> tasks/get
-> tasks/update / tasks/cancel as supported
-> terminal result
This decouples durable work from one connection lifetime.
See MCP Tasks Explained.
Request identifiers are not business idempotency keys
JSON-RPC id identifies a request/response exchange.
Do not assume reusing the same JSON-RPC ID guarantees side-effect deduplication.
If the application/server needs idempotency, use a mechanism explicitly designed for that operation.
Protocol-version handling must be explicit
A server may support:
modern 2026-07-28 behavior
older initialize/session behavior
both through compatibility mode
The client should not mix lifecycle rules accidentally.
For example:
modern request + legacy Mcp-Session-Id assumptions
is a sign that version handling is leaking.
Keep a protocol profile per connection/request path.
Migrating from legacy HTTP+SSE
Legacy HTTP+SSE transport is deprecated in the current protocol generation.
A migration path should avoid one giant compatibility abstraction.
Separate transports behind an interface:
interface MCPTransport {
send(request: MCPRequest): AsyncResult<MCPResponse>
listen?(filter: SubscriptionFilter): AsyncSequence<MCPNotification>
}
Then implement:
LegacySSETransport
ModernStreamableHTTPTransport
with version-specific behavior inside each.
Do not sprinkle if legacy across conversation logic.
Request cancellation is best effort
If the user cancels while an HTTP request is in flight, the client can abort its connection/request.
That does not prove the server stopped processing.
For side-effecting operations, cancellation should mean:
client no longer waiting
not necessarily:
operation definitely did not happen
If the server exposes a task or cancellation operation, reconcile authoritative state.
Timeouts should be layered
Useful timeout categories include:
connect timeout
TLS/auth timeout
first-response timeout
idle-stream timeout
overall operation timeout
task polling deadline
subscription reconnect backoff
One global 30-second timeout is rarely enough.
See Timeout Design for AI Applications when that article is published.
Proxy behavior matters
Remote MCP may sit behind:
- CDN;
- reverse proxy;
- API gateway;
- corporate proxy;
- service mesh.
Streaming/subscription behavior can be affected by:
- buffering;
- idle timeouts;
- maximum request duration;
- header stripping;
- auth rewriting.
When debugging, show the resolved endpoint and protocol stage, not only “MCP failed.”
TLS is part of server identity
For remote servers, validate HTTPS normally.
Do not disable certificate validation just because the server is an MCP endpoint.
A custom endpoint should not get weaker TLS policy than any other API connection.
Redirects require care
Automatic redirects can change origin.
Before forwarding authorization credentials to a redirected destination, apply normal secure HTTP rules.
Do not blindly preserve bearer tokens across arbitrary cross-origin redirects.
Do not log sensitive headers
Diagnostics are useful, but redact:
Authorization
Cookie
private custom headers
token-like query parameters
It is usually safe to log protocol headers such as:
MCP-Protocol-Version
Mcp-Method
Mcp-Name
provided names themselves are not sensitive in your application context.
Trace operation stages separately
A useful trace can include:
resolve connection
obtain/refresh token
serialize MCP request
connect
server response start
parse JSON-RPC
normalize result
For subscription:
listen opened
acknowledged filter
notification received
stream closed
reconnect scheduled
This is much easier to debug than one duration value.
Header mismatch tests are essential
Test that your serializer rejects or catches:
Mcp-Method != JSON body method
Mcp-Name != body name
missing protocol version
missing required name header
malformed mirrored parameter header
These are protocol correctness tests, not just networking tests.
Test load-balanced statelessness
A powerful integration test alternates requests between server instances.
request 1 -> instance A
request 2 -> instance B
request 3 -> instance C
If the modern client/server unexpectedly depends on hidden session state, this test exposes it quickly.
Test unknown outcomes
Simulate:
server commits side effect
connection closes before response
Verify the client does not blindly repeat the operation.
This is one of the most important reliability tests for any HTTP-based tool system.
A clean transport boundary
The tool coordinator should not care how TLS sockets, redirects, or JSON framing are implemented.
The HTTP layer should not decide whether a tool call is safe to retry.
Where BYOKchat fits
A provider-neutral client can treat MCP Streamable HTTP as one transport implementation beneath its MCP tool layer.
The application can keep:
- server configuration durable;
- credentials in secure storage;
- tool rounds in local conversation state;
- permission decisions in app policy;
- HTTP requests transient;
- subscription streams reconnectable;
- Tasks durable when needed.
That separation matches the modern stateless MCP architecture and avoids tying user workflows to one network connection.