MCP 2026-07-28

Stateless, Streamable & How We Uplifted

Matthew Khouzam | Ericsson Research | August 2026

The Problem with 2025-03-26

Why the spec needed to change

Session State Pain

  • Long-lived sessions server accumulates per-client state
  • Sticky sessions SSE requires load-balancer affinity
  • Orphaned state client drops = no cleanup path
  • No horizontal scaling session pinned to one instance

Transport Limitations

  • SSE required two connections: GET (stream) + POST (requests)
  • Not supported by many corporate proxies
  • No request-response correlation built-in
  • stdio only fine for local, useless for remote

SSE was a workaround, not a design choice. It leaked complexity into every deployment.

What's New in 2026-07-28

The headline changes

Fully Stateless Protocol

  • No more initialize/initialized handshake
  • No more Mcp-Session-Id header (removed entirely)

2025-03-26 made sessions optional. 2026-07-28 removes them entirely.

Self-Describing by Design

  • Every request carries protocol version + capabilities in _meta
  • New server/discover RPC for up-front capability discovery
  • Enables serverless, CDN, and horizontal scaling

Self-Describing Requests

  • Each request includes _meta.io.modelcontextprotocol/protocolVersion
  • Each request includes _meta.io.modelcontextprotocol/clientCapabilities
  • Clients SHOULD include _meta.io.modelcontextprotocol/clientInfo
  • Servers SHOULD include _meta.io.modelcontextprotocol/serverInfo in results

OAuth 2.1 & Security

  • Mandatory for remote HTTP servers
  • PKCE required no client secrets in public clients
  • Dynamic client registration deprecated in favor of Client ID Metadata Documents
  • Resource indicators for token scoping
  • Metadata discovery via /.well-known/oauth-authorization-server

Enterprise-Managed Authorization (EMA)

  • EMA extension IT admins centrally provision MCP server access via IdP
  • Users auto-connected to required servers on login no per-app OAuth prompts
  • Incremental scope consent servers request additional permissions mid-session via WWW-Authenticate
  • Robust issuer validation per RFC 9207

Direct response to enterprise feedback: orgs couldn't adopt MCP without central IT control over which servers employees connect to.

Multi Round-Trip Requests (MRTR)

  • Replaces server-initiated requests (sampling, elicitation, roots)
  • Server returns InputRequiredResult with resultType: "input_required"
  • Client retries original request with inputResponses
  • All results now carry a resultType field

Servers no longer initiate JSON-RPC requests. Client always drives the interaction.

More Changes

  • subscriptions/listen replaces GET endpoint and resource subscriptions
  • Removes ping, logging/setLevel, notifications/roots/list_changed
  • Deprecates Roots, Sampling, and Logging features
  • Tasks moved to official extension (io.modelcontextprotocol/tasks)
  • Cache hints ttlMs and cacheScope on list results

Subscriptions Pattern

subscriptions/listen consolidates all server→client notifications into one stream:

  • Replaces the old GET endpoint and per-resource subscriptions
  • Client opens a single listener; server pushes change notifications
  • Works with stateless model — no stored subscription state on server
  • Covers: resource changes, tool list updates, prompt list updates

One unified pattern for all server-initiated notifications. No more per-feature subscription APIs.

Spec: modelcontextprotocol.io/specification/2026-07-28/basic/patterns/subscriptions

Extensions Framework

Advanced behaviours modelled as optional, self-contained modules:

MCP Apps
  • Server declares interactive HTML UI
  • Rendered in sandboxed iframe in chat
  • Bidirectional comms via postMessage + JSON-RPC
  • Graceful degradation to plain text
Tasks
  • Server returns durable taskId
  • Client polls via tasks/get
  • Mid-flight input via tasks/update
  • Survives connection drops

HTTP Routing Headers

New headers for infrastructure-level routing:

POST /mcp HTTP/1.1
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: search
Content-Type: application/json

Mcp-Method and Mcp-Name let load balancers and gateways route traffic by operation without parsing the JSON body.

Stateless Protocol, Stateful Applications

Use explicit handles instead of hidden session state:

1. Client calls: create_checkout({items: ["widget-a", "widget-b"]})
   Server returns: {basket_id: "bsk_8f3a", status: "created"}

2. Client calls: add_shipping(basket_id: "bsk_8f3a", address: {...})
   Server returns: {basket_id: "bsk_8f3a", status: "ready_to_pay"}

3. Client calls: confirm_order(basket_id: "bsk_8f3a")
   Server returns: {order_id: "ord_91cb", status: "confirmed"}

The LLM can reason about explicit handles, compose them across tools, and hand them off between workflow steps. Hidden session state was invisible to the model.

Protocol Deep-Dive

From sessions to stateless

Before vs After (Protocol)

Aspect2025-03-26 (Old)2026-07-28 (New)
Initializationinitialize/initialized handshakeNo handshake; self-describing requests
SessionOptional (Mcp-Session-Id MAY)Removed entirely
CapabilitiesExchanged once at initCarried per-request in _meta

Before vs After (Architecture)

Aspect2025-03-26 (Old)2026-07-28 (New)
Server discoveryVia initialize responseserver/discover RPC
Server requestsServer MAY send requests to clientRemoved; MRTR pattern instead
TransportStreamable HTTP + stdioStreamable HTTP + stdio (unchanged)

Self-Describing Request

// Every request now carries its own context
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "list_experiments",
    "_meta": {
      "io.modelcontextprotocol/protocolVersion": "2026-07-28",
      "io.modelcontextprotocol/clientCapabilities": {},
      "io.modelcontextprotocol/clientInfo": {
        "name": "MyClient", "version": "1.0"
      }
    }
  },
  "id": 1
}

Multi Round-Trip Requests

Old (2025-03-26): Server sends request to client

Server → elicitation/create request to client
Client → responds with user input

New (2026-07-28): Server returns InputRequiredResult

Client → POST tools/call
Server → InputRequiredResult (resultType: "input_required")
Client → retries with inputResponses
Server → final result (resultType: "complete")

Case Study: TMLL

Python harness-level uplift

TMLL Before & After

Before (SDK 1.27.0)

from mcp.server.fastmcp import FastMCP
from mcp.server.fastmcp import Image

mcp = FastMCP("tmll-cli-mcp-server")

After (SDK 2.0.0)

from mcp.server import MCPServer
from mcp.server.mcpserver import Image

mcp = MCPServer("tmll-cli-mcp-server")

Core change: FastMCPMCPServer class rename. API surface identical.

TMLL What Changed

  • Import paths mcp.server.fastmcpmcp.server / mcp.server.mcpserver
  • Monkey-patching removed no more _mcp_server internal access
  • ToolAnnotations added for safety hints
  • Transport unchanged stdio stays as-is
  • Tool registration unchanged @mcp.tool() decorators work the same

TMLL Tool Annotations (Read-Only)

from mcp.types import ToolAnnotations

@mcp.tool(annotations=ToolAnnotations(
    readOnlyHint=True,
    destructiveHint=False,
    openWorldHint=False
))
async def list_experiments() -> str:
    """List all open experiments."""
    return await run_cli(["list-experiments"])

TMLL Tool Annotations (Open World)

@mcp.tool(annotations=ToolAnnotations(
    readOnlyHint=True,
    destructiveHint=False,
    openWorldHint=True
))
async def detect_anomalies(...) -> str:
    """Run anomaly detection on trace data."""
    ...

14 tools total: most readOnly. Only delete_experiment and ensure_server are destructive. detect_anomalies is openWorld (contacts trace server).

TMLL Complexity Assessment

Uplift complexity: LOW

  • Import renames (mechanical)
  • Remove monkey-patching (simplification)
  • Add ToolAnnotations (optional, additive)

TMLL Why It Was Easy

  • No transport changes (stdio unchanged in spec)
  • No session logic to remove (was already stateless)
  • No server-initiated requests to refactor

Total: 1 commit, ~20 lines changed

If your server is stdio-only and stateless, the uplift is trivial.

Case Study: Theia

MCP harness (client) uplift

Why Harnesses Are Harder

  • Harness = MCP client that connects to many servers
  • Must support servers at 2024-11-05, 2025-03-26, and 2026-07-28
  • Also exposes itself as a server (dual role)

A server only needs to speak one protocol version. A harness must speak all of them.

Multi-Version Testing

  • Needs backward-compatible transport negotiation
  • Must be tested against servers at each spec version
  • Protocol detection: try stateless first, fall back to initialize
  • Testing matrix multiplies with each supported version

Theia Architecture

MCP Client
Connect TO external servers
  • StreamableHTTP (primary)
  • SSE (fallback)
  • stdio (local)
  • OAuth 2.1 + PKCE
MCP Server
Expose Theia AS a server
  • Stateless StreamableHTTP
  • Single /mcp endpoint
  • Per-request transport
  • Tools + Resources + Prompts

Theia Server Stateless Uplift

Before

// Session-tracking transport
const transport =
  new StreamableHTTPServerTransport({
    sessionIdGenerator: () =>
      randomUUID(),
  });
// Map of active sessions
httpTransports.set(
  sessionId, transport
);

After (2026-07-28)

// Stateless  no session tracking
const transport =
  new StreamableHTTPServerTransport({
    sessionIdGenerator: undefined,
  });
// Per-request, no map needed
// Transport created and disposed
// within request handler

Theia Client Transport Negotiation

async doStart(): Promise<void> {
  try {
    // Try Streamable HTTP first (2026-07-28)
    this.transport = new StreamableHTTPClientTransport(
      url, { authProvider: this.oauthProvider }
    );
    await this.client.connect(this.transport);
  } catch (e) {
    // Fall back to SSE for legacy servers
    this.transport = new SSEClientTransport(
      url, { authProvider: this.oauthProvider }
    );
    await this.client.connect(this.transport);
  }
}

Graceful degradation: StreamableHTTP → SSE fallback → stdio for local

Theia OAuth 2.1 Components

  • MCPOAuthClientProvider tokens, PKCE, dynamic registration
  • Credential store keyed by server name + URL
  • Callback service handles OAuth redirects

Theia OAuth 2.1 Modes

  • Interactive mode launches browser for consent
  • Non-interactive mode autostart with stored tokens

+8,190 lines for the full OAuth stack

OAuth is the biggest single addition. Required credential store, callback handlers for browser and Electron, token refresh logic, and server lifecycle integration.

Theia Plugin API (Registration)

// Plugin authors register MCP server definitions
vscode.lm.registerMcpServerDefinitionProvider(
  'my-tools', {
  provideMcpServerDefinitions():
    McpServerDefinition[] {
    return [{
      type: 'http',
      name: 'My Remote Server',
      url: 'https://my-server.com/mcp',
      // OAuth handled automatically by Theia
    }];
  }
});

Theia Plugin API (Types)

Supports two server definition types:

McpHttpServerDefinition
  • type: 'http'
  • url server endpoint
  • OAuth handled by IDE
McpStdioServerDefinition
  • type: 'stdio'
  • command + args
  • Local process managed by IDE

Theia Complexity Assessment

Uplift complexity: HIGH

  • Stateless server transport (moderate remove session map)
  • StreamableHTTP client + SSE fallback (moderate)
  • OAuth 2.1 full stack (high +8K lines)
  • Plugin API types for HTTP servers (low)
  • SDK bump to 1.30.0 (low)

Total: multiple PRs, ~10K lines changed

Deprecation Policy (SEP-2596 & SEP-2577)

Formal framework for protocol evolution

The Policy (SEP-2596)

  • 12-month minimum deprecation window before removal
  • Public registry of deprecated features with clear timelines
  • Predictable migration windows for implementers

What's Deprecated (SEP-2577)

FeatureWhy DeprecatedMigration Path
RootsFilesystem assumptions don't generalise to remote/cloudPass paths as tool params or server config
SamplingReverse dependency; complicates trust boundariesServers call LLM APIs directly
LoggingRedundant with existing observability infraUse stderr or OpenTelemetry
Dynamic Client Reg.Conflicts with enterprise-managed authExplicit OAuth app registration
HTTP+SSE TransportSuperseded by Streamable HTTPMigrate to Streamable HTTP

Not a removal notice - these still work today. Earliest removal: July 28, 2027.

Timeline

DateMilestone
May 2026Release candidate published
July 28, 2026Final specification ships
July 28, 2027Earliest removal date for deprecated features

Beta SDKs available for Python, TypeScript, Go, and C#

Migration Checklist

What you need to do

Server-Side Checklist (Protocol)

  • Bump SDK Python: mcp>=2.0.0 · TS: @modelcontextprotocol/sdk@^1.30.0
  • Remove initialize handler no more handshake logic
  • Remove session tracking Mcp-Session-Id is gone

Server-Side Checklist (Features)

  • Implement server/discover required RPC for capability advertisement
  • Return resultType on all results ("complete" or "input_required")
  • Replace server-initiated requests with MRTR pattern (InputRequiredResult)

Client-Side Checklist (Protocol)

  • Include _meta on every request (protocol version, capabilities)
  • Call server/discover for initial capability probing
  • Remove initialize/initialized exchange

Client-Side Checklist (Features)

  • Handle InputRequiredResult retry with inputResponses
  • Use subscriptions/listen for server notifications
  • Backward compat detect older servers, fall back to initialize

Testing & Validation

  • MCP Inspector interactive testing tool
  • Verify requests work without initialize handshake
  • Test server/discover returns correct capabilities

npx @modelcontextprotocol/inspector test your server interactively

Testing & Validation (cont.)

  • Validate resultType on all tool call responses
  • Test MRTR flow if using elicitation/sampling
  • Backward compat: test against 2025-03-26 servers
  • Verify _meta fields propagate correctly

Uplift Spectrum

DimensionTMLL (Python)Theia (TypeScript)
RoleMCP Server onlyClient + Server
Transportstdio (unchanged)StreamableHTTP + SSE + stdio
SDKPython mcp 2.0.0TS @mcp/sdk 1.30.0
SessionAlready statelessRemoved session tracking
OAuthN/A (local stdio)Full OAuth 2.1 + PKCE
Tools14 (decorator-based)Dynamic (contribution API)
AnnotationsAdded (readOnly, etc.)Inherited from tool providers
Effort~1 day~2 weeks

Key Takeaways

What this means for us

The Big Picture

  • Stateless = simpler servers easier horizontal scaling, serverless-friendly
  • Streamable HTTP = modern HTTP CDN-friendly, proxy-safe, standard semantics
  • OAuth 2.1 = production security required for remote, enterprise-grade
  • The spec rewards read-only, annotation-rich tool design
  • stdio stays unchanged simplest path for local tools

Recommendations

  • Start with annotations low effort, high value for clients
  • Design stateless-first avoid server-side session state
  • Use outputSchema enables reliable agentic workflows
  • Plan for OAuth if deploying remote servers
  • Test with Inspector before claiming compliance

References