TL;DR
MCP 2026-07-28 is not a small update to the 2025 protocol. It introduces a modern, stateless era: no initialize handshake, no protocol-level Session ID, no GET event stream, and no SSE resume. Every request declares its protocol version, client identity, and capabilities; servers expose server/discover; server-to-client requests become Multi Round-Trip Request results; and optional features move into negotiated extensions. Upgrade through a dual-era compatibility layer, not an in-place header rename.
Table of Contents
- Version Timeline
- The Modern Stateless Model
- Streamable HTTP Changes
- Multi Round-Trip Requests
- OAuth Authorization Changes
- Skills and Tasks Extensions
- Tool Contracts and Cache Hints
- Runnable Request Validator
- Dual-Era Migration Plan
- Conformance and Abuse Tests
- Frequently Asked Questions
- Primary Sources
Version Timeline
Read every MCP claim against a pinned revision. Features added in one 2025 revision may be changed, deprecated, or removed in 2026.
| Revision | High-impact changes |
|---|---|
2025-03-26 |
introduced Streamable HTTP, OAuth-based authorization, tool annotations, and JSON-RPC batching |
2025-06-18 |
removed JSON-RPC batching; added structured tool output, protected-resource metadata, Resource Indicators, and elicitation |
2025-11-25 |
added OIDC discovery, incremental scope consent, Client ID Metadata Documents, URL elicitation, and experimental tasks |
2026-07-28 |
removed initialization and protocol sessions; added per-request metadata, server/discover, MRTR, subscriptions/listen, required resultType, cache hints, and negotiated extensions |
This history exposes a practical rule: MCP version is part of the wire contract, not documentation metadata. Store it alongside SDK, transport, and authorization-profile versions.
The Modern Stateless Model
MCP calls revision 2026-07-28 and later modern; versions through 2025-11-25 are legacy because they establish state through initialize.
Every modern request includes these _meta fields:
io.modelcontextprotocol/protocolVersionio.modelcontextprotocol/clientCapabilities- preferably
io.modelcontextprotocol/clientInfo
Every result contains resultType. Ordinary results use "complete"; MRTR uses "input_required"; negotiated extensions may add values such as "task".
Servers must implement server/discover to advertise supported versions, capabilities, and identity. Clients may call it before normal requests or optimistically send a preferred version and handle UnsupportedProtocolVersionError (-32022).
Stateless does not mean an implementation cannot own business state. It means protocol state is not implicit in a connection or Mcp-Session-Id. A tool that needs continuity must return an explicit server-minted handle and receive it in later arguments.
Streamable HTTP Changes
Modern Streamable HTTP uses one endpoint and one POST per JSON-RPC request or notification. The response is either one JSON object or a request-scoped SSE stream.
The 2026-07-28 revision removes:
- the GET stream endpoint
Mcp-Session-Id- SSE event IDs and
Last-Event-IDreplay - independent server-initiated JSON-RPC requests
It adds or requires:
MCP-Protocol-Versionon each POST- matching protocol version in request-body
_meta Mcp-Methodfor requestsMcp-Namefortools/call,resources/read, andprompts/get- cancellation by closing the request's SSE response stream
subscriptions/listenfor opted-in long-lived change notifications
If an in-flight SSE response breaks, the client cannot resume it. The client must decide whether the logical operation is safe to issue again, create a new JSON-RPC ID, and rely on application idempotency for side effects.
Transport security still requires Origin validation. An invalid Origin must receive HTTP 403. Local servers should bind to loopback rather than all interfaces.
Multi Round-Trip Requests
MRTR replaces separate server-initiated requests for Sampling, Elicitation, and Roots.
When a server needs additional input:
- it returns
resultType: "input_required" inputRequestscontains one or more requested interactions- optional opaque
requestStatepreserves server correlation - the client gathers approved responses
- the client retries the original method with a new JSON-RPC ID,
inputResponses, andrequestState
The client must treat requested model sampling, user elicitation, or roots access according to its normal trust and consent model. MRTR changes message flow; it does not grant the server those capabilities.
Validate:
- each input-request key is unique and recognized
- retries use a new request ID
requestStateis treated as opaque and bounded- repeated or already-satisfied keys are rejected or deduplicated
- cancellation and deadline still cover the full logical operation
OAuth Authorization Changes
Authorization remains optional for MCP as a whole. HTTP implementations that support authorization should follow the MCP profile; stdio implementations should obtain credentials from the environment rather than running the HTTP OAuth flow.
For a protected remote MCP server:
- the MCP server acts as an OAuth resource server
- it publishes RFC 9728 Protected Resource Metadata
- the client discovers and validates authorization-server metadata
- the client obtains a client ID
- authorization and token requests include RFC 8707
resource - each MCP HTTP request carries
Authorization: Bearer ... - the server validates issuer, audience, expiry, signature, and scope
Client registration priority
MCP 2026-07-28 defines this preferred order:
- pre-registered credentials when available
- Client ID Metadata Documents when the authorization server advertises support
- RFC 7591 Dynamic Client Registration as a deprecated compatibility fallback
- user-provided client information
Persisted client credentials must be keyed by authorization-server issuer and must not be reused after the issuer changes. Client ID Metadata Document identifiers are self-hosted HTTPS URLs and follow their own validation rules.
Scope and issuer validation
Use the scope in WWW-Authenticate as authoritative for the current operation when present. A runtime 403 with insufficient_scope can trigger step-up authorization, but the client should request the union of newly required and previously granted scopes.
Authorization servers should return RFC 9207 iss; clients must validate a present value against the issuer recorded before redirect. Resource servers must reject tokens not intended for their own canonical resource. Never forward an incoming token to a downstream service unless token exchange or another explicit delegation design authorizes it.
Skills and Tasks Extensions
MCP 2026-07-28 introduces an extensions capability map. An extension is usable only when the required party declares it.
Skills
The io.modelcontextprotocol/skills extension publishes Agent Skills through Resources:
skills/listenumerates Skill entriesskills/getreturns one entryresources/readretrievesSKILL.mdand supporting files- optional
resources/directory/readlists directory children
Skill identity is the pair of originating server identity and Skill URI. File digests protect integrity during the acting window but do not establish publisher trust. Activating a nested Skill requires fresh consent.
Tasks
The io.modelcontextprotocol/tasks extension currently augments tools/call. A server may return resultType: "task" with a durable task handle after the client advertises support. Clients then use:
tasks/getto polltasks/updateto answer outstanding input requeststasks/cancelto request cancellation
The server decides whether to materialize a task. A task handle must not be returned until tasks/get can resolve it. Task IDs, TTLs, polling intervals, authorization, and terminal results need durable storage and tenant isolation.
Tool Contracts and Cache Hints
Modern tool descriptors use JSON Schema and may declare outputSchema. If an output schema exists, servers must return conforming structuredContent, and clients should validate it. For backward compatibility, structured results should also provide serialized text content.
Tool annotations remain hints, not security controls. Read-only, destructive, idempotent, and open-world declarations cannot replace server-side authorization or side-effect checks.
List and read results now carry:
ttlMs: freshness hint in millisecondscacheScope:"public"or"private"
Cache keys still need protocol version, authorization context, origin, method, name or URI, cursor, and relevant arguments. A freshness hint is not permission to share private data.
The x-mcp-header JSON Schema annotation can mirror selected primitive tool parameters into Mcp-Param-* HTTP headers. Clients must reject invalid annotations and should never mirror secrets or personal data into headers.
Runnable Request Validator
The following standard-library Go program builds and validates one modern tools/list request. A production client must also emit matching HTTP headers and validate the full schema, but this example makes the per-request metadata invariant executable.
package main
import (
"encoding/json"
"errors"
"fmt"
"log"
)
const protocolVersion = "2026-07-28"
type ClientInfo struct {
Name string `json:"name"`
Version string `json:"version"`
}
type Meta struct {
ProtocolVersion string `json:"io.modelcontextprotocol/protocolVersion"`
ClientInfo ClientInfo `json:"io.modelcontextprotocol/clientInfo"`
ClientCapabilities map[string]any `json:"io.modelcontextprotocol/clientCapabilities"`
}
type Params struct {
Cursor string `json:"cursor,omitempty"`
Meta Meta `json:"_meta"`
}
type Request struct {
JSONRPC string `json:"jsonrpc"`
ID int `json:"id"`
Method string `json:"method"`
Params Params `json:"params"`
}
func validateRequest(request Request, headerVersion string) error {
if request.JSONRPC != "2.0" {
return errors.New("jsonrpc must be 2.0")
}
if request.ID == 0 || request.Method == "" {
return errors.New("request id and method are required")
}
if request.Params.Meta.ProtocolVersion != protocolVersion {
return fmt.Errorf(
"unsupported protocol version %q",
request.Params.Meta.ProtocolVersion,
)
}
if headerVersion != request.Params.Meta.ProtocolVersion {
return errors.New("MCP-Protocol-Version header mismatch")
}
if request.Params.Meta.ClientInfo.Name == "" {
return errors.New("clientInfo.name is required by local policy")
}
if request.Params.Meta.ClientCapabilities == nil {
return errors.New("clientCapabilities must be present")
}
return nil
}
func main() {
request := Request{
JSONRPC: "2.0",
ID: 1,
Method: "tools/list",
Params: Params{
Meta: Meta{
ProtocolVersion: protocolVersion,
ClientInfo: ClientInfo{
Name: "example-client",
Version: "1.0.0",
},
ClientCapabilities: map[string]any{
"extensions": map[string]any{},
},
},
},
}
if err := validateRequest(request, protocolVersion); err != nil {
log.Fatal(err)
}
body, err := json.MarshalIndent(request, "", " ")
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
}
The request also needs these HTTP headers:
POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2026-07-28
Mcp-Method: tools/list
For tools/call, add Mcp-Name from params.name. The HTTP version and body version must match.
Dual-Era Migration Plan
1. Inventory actual behavior
Record every client and server's protocol revisions, SDK version, transport, authorization profile, extensions, session use, server-initiated requests, streaming, and retry behavior. Do not infer support from package version alone.
2. Add a protocol boundary
Centralize version detection, request metadata, headers, result decoding, and transport behavior. Business tools should not know whether the peer is modern or legacy.
3. Implement modern behavior
Add server/discover, per-request metadata, required HTTP headers, resultType, request-scoped streaming, MRTR, subscription streams, cache hints, and selected extensions.
4. Isolate legacy behavior
A dual-era implementation can fall back to initialize and legacy Streamable HTTP only after the transport-specific probe identifies a legacy server. Cache the detected era per stdio process or HTTP origin and re-probe when the assumption fails.
5. Remove implicit session state
Replace Mcp-Session-Id state with explicit, server-minted handles passed as normal arguments. Bind each handle to identity, tenant, expiry, and operation class.
6. Shadow and canary
Replay a sanitized conformance corpus against both paths. Compare logical results, authorization decisions, stream behavior, retries, and side effects. Route a small cohort to modern mode and keep independent rollback.
7. Retire legacy only with evidence
Measure real client versions and negotiation failures. Announce an end date, provide actionable errors, and remove legacy code only after supported traffic reaches the agreed threshold.
Conformance and Abuse Tests
| Area | Required tests |
|---|---|
| versioning | supported version, unsupported version list, body/header mismatch |
| lifecycle | modern request without initialize, legacy fallback, ambiguous errors |
| transport | JSON response, SSE response, close-as-cancel, no replay |
| MRTR | new request ID, opaque state, duplicate keys, denied user input |
| subscriptions | opt-in types, subscription ID, disconnect cleanup |
| authorization | metadata discovery, resource/audience, issuer binding, scope step-up |
| tools | schema validation, output validation, annotation distrust, deterministic list |
| caching | TTL expiry, private scope, auth-aware keys, list-change invalidation |
| Skills | origin-plus-URI identity, digest mismatch, nested consent, path traversal |
| Tasks | capability gate, durable creation, poll interval, tenant isolation, cancellation |
| side effects | idempotency, retry after broken stream, duplicate request protection |
Run the matrix against real SDK combinations. A schema-only test cannot prove proxy behavior, cancellation, authentication, or duplicate side-effect safety.
Frequently Asked Questions
Can a 2026 client send initialize for compatibility?
A dual-era client can probe and fall back according to the transport-specific rules. A modern-only client does not use initialize. Do not send both modern and legacy lifecycle messages speculatively on the same execution path.
Does stateless MCP remove all server state?
No. It removes implicit protocol session state. Servers may keep business state, jobs, subscriptions, caches, or explicit handles, but those objects need normal identity, authorization, expiry, and lifecycle rules.
Is SSE removed?
No. Streamable HTTP can return a request-scoped SSE stream, and subscriptions/listen uses a long-lived SSE response. The removed pieces are the standalone GET stream, protocol sessions, and SSE resume through event IDs.
Can cache hints be trusted across users?
Only when the result is public and the complete cache key is authorization-independent. cacheScope: "private" must not enter shared caches, and even public data needs version, origin, method, name, cursor, and argument dimensions.
Are Skills executable code?
A Skill is an instruction directory with supporting resources. It may reference scripts, but the host decides whether and how anything runs. The MCP Skills extension transports files; it does not bypass consent, sandboxing, or tool authorization.
Summary
MCP 2026-07-28 creates a cleaner stateless protocol, but it is a real compatibility boundary. Modern requests carry their own version and capabilities, HTTP streams are request-scoped, additional interaction uses MRTR, and optional systems such as Skills and Tasks are negotiated extensions. Migrate with explicit era detection, strict OAuth binding, idempotent retries, and a full conformance matrix.
Related Resources
- MCP Protocol Deep Dive
- MCP Tool Design Best Practices
- Build a Secure Skill Runtime with Eino and MCP
- MCP Glossary
- OAuth Glossary
Primary Sources
- MCP 2026-07-28 specification
- MCP 2026-07-28 changelog
- MCP versioning and compatibility
- MCP Streamable HTTP
- MCP authorization profile
- MCP Skills extension
- MCP Tasks extension
- RFC 9728: OAuth 2.0 Protected Resource Metadata
- RFC 8707: Resource Indicators for OAuth 2.0
- RFC 9207: OAuth 2.0 Authorization Server Issuer Identification