MCP 2026-07-28 Spec
View SourcePurpose
Map every feature in the MCP 2026-07-28 specification — the modern era —
against what wymcp implements, to guide planning. Companion to
mcp-spec-2025-11-25-overview.md (the legacy era); the pair is the
yardstick while wymcp is dual-era. Era vocabulary: docs/glossary.md
(era, lane, era classification, protocol fields).
Status column: the modern core is implemented; scope notes name the planned change that owns each remaining gap.
1. Base Protocol
1.1 The stateless model
The modern era has no handshake and no sessions (SEP-2575, SEP-2567). Every request is self-contained: servers MUST NOT rely on prior requests for context. Cross-call state is the server's own business, carried as explicit handles in ordinary tool arguments.
| Feature | Spec requirement | wymcp status |
|---|---|---|
Per-request protocol fields in _meta (io.modelcontextprotocol/protocolVersion, …/clientCapabilities) | MUST (both required on every request) | ✅ Wymcp.Plugs.Era classifies on them; Wymcp.Plugs.ProtocolFields enforces both |
Reject missing protocol fields with -32602 + HTTP 400 | MUST | ✅ Wymcp.Plugs.ProtocolFields (data.reason names the missing field) |
io.modelcontextprotocol/clientInfo on requests | SHOULD (client-side) | N/A by design: stateless, nothing stored |
io.modelcontextprotocol/serverInfo in every result's _meta | SHOULD | ✅ Wymcp.Modern stamps it (built by Wymcp.ServerInfo from the :server_info router opt + app config) |
resultType on every result ("complete" / "input_required"; extensions may add values) | MUST | ✅ "complete" on every modern result ("input_required" arrives with the planned MRTR change) |
Unknown method → HTTP 404 + -32601 | MUST | ✅ modern dispatch table (legacy lane still answers 200 — era-varying cell) |
Error-code allocation: -32020..-32099 spec-reserved, -32000..-32019 implementation-defined | MUST NOT misallocate | ✅ -32022 allocated; legacy -32001 stays legacy-lane-only |
1.2 Version negotiation & era classification
| Feature | Spec requirement | wymcp status |
|---|---|---|
server/discover — supportedVersions, capabilities (incl. extensions), optional instructions; serverInfo in result _meta; cache hints | MUST implement (clients MAY skip) | ✅ Wymcp.Methods.Discover |
UnsupportedProtocolVersionError -32022 + 400 with data.{supported, requested} | MUST | ✅ Wymcp.Plugs.ProtocolFields |
MissingRequiredClientCapabilityError -32021 + 400 with data.requiredCapabilities | MUST | ❌ no core emitter (first emitter arrives with the planned MRTR change) |
Dual-era serving on one endpoint (modern _meta → stateless; initialize → legacy session semantics) | MAY | ✅ Wymcp.Plugs.Era (its moduledoc carries the classification table): era-mix fails closed; no-signal requests classify legacy with a dual-instruction rejection |
| Era determination cached per origin | Client-side | N/A |
1.3 Streamable HTTP transport (modern)
| Feature | Spec requirement | wymcp status |
|---|---|---|
| Single endpoint, POST-only | MUST | ✅ the modern lane is POST-only (GET/DELETE routes remain legacy-lane machinery until the legacy drop; a modern-only server would answer 405) |
Response: application/json or SSE response stream | MUST offer one; client MUST accept both | ✅ answers plain JSON (no modern SSE responses — no progress/logging in core scope) |
SSE event IDs / Last-Event-ID resumption | Removed; ignore the header | ✅ the modern lane never opens a stream (legacy lane keeps its counter behavior) |
| Closing the SSE response stream = cancellation | MUST treat disconnect as cancellation | N/A in core scope (no modern SSE responses) |
Origin validation | MUST | ✅ origin check — wire checks are era-invariant, run before era classification |
| Auth per request | Stateless model | ✅ auth check — era-invariant |
MCP-Protocol-Version header on every POST, mirroring the body field; Mcp-Method; Mcp-Name; Mcp-Param-* via x-mcp-header; mismatch/missing → -32020 + 400 | MUST | ❌ a planned header-binding change (core reads the protocol fields as authoritative and ignores these headers) |
| Notification POSTs → 202 | MUST | ✅ 202-and-drop (the modern core defines no client→server notifications over HTTP) |
1.4 Authorization
Unchanged model for wymcp's scope (Bearer via the Wymcp.Auth
behaviour). 2026-07-28 auth deltas (RFC 9207 iss validation, Client ID
Metadata Documents, application_type in DCR) are client-side or
OAuth-server-side — no wymcp surface today.
2. Server Features
2.1 Tools
| Feature | Spec requirement | wymcp status |
|---|---|---|
tools/list | MUST if capability declared | ✅ served from compile-time tools; runtime tools are legacy-lane-only |
tools/call | MUST if capability declared | ✅ session-free Context (session_pid: nil by construction) |
Unknown tool name → -32602 (InvalidParamsError) | Defined | ✅ era-varying cell: the legacy lane keeps its pre-existing -32601 |
| Tool set MUST NOT vary per-connection (MAY vary by authorization) | MUST | ✅ holds by construction: compile-time list |
Deterministic tools/list order | SHOULD | ✅ by construction (compile-time order) |
Cache hints on tools/list (ttlMs, cacheScope) | MUST | ✅ framework defaults ttlMs: 300_000, cacheScope: "private" (Wymcp.Modern) |
listChanged capability + notifications/tools/list_changed over subscriptions/listen | MAY | ❌ deferred — modern lane declares no listChanged; TTL-only freshness (spec-sanctioned); a subscriptions/listen implementation is planned |
Pagination (cursor / nextCursor) | SHOULD | ❌ (missing on the legacy lane too — era-neutral gap) |
inputSchema/outputSchema loosened to full JSON Schema 2020-12; $ref no network deref; composition bounds | MUST (bounds SHOULD) | ✅ JSV validates full 2020-12 drafts and wymcp-generated schemas are simple |
structuredContent may be any JSON value | Defined | ❌ core (the legacy lane still assumes an object) |
x-mcp-header argument annotations | MAY (invalid ⇒ client drops tool) | ❌ a planned header-binding change |
2.2 Resources, Prompts, Completion
Unimplemented in wymcp on both lanes (era-neutral gaps — legacy overview
§§2.2–2.3, §4.5). Modern deltas when they arrive: results become
cacheable (ttlMs/cacheScope MUST on the five list/read operations),
resources/subscribe is replaced by subscriptions/listen,
resource-not-found is -32602 (was -32002).
3. Server-initiated interaction (MRTR)
Server→client JSON-RPC requests are forbidden in the modern era. MRTR
(SEP-2322) replaces them: only tools/call, resources/read, and
prompts/get may answer resultType: "input_required" with
inputRequests (ElicitRequest / CreateMessageRequest / ListRootsRequest)
and/or opaque requestState; the client retries the original request
(new JSON-RPC id) threading inputResponses. Interim results are never
cacheable. Servers MUST NOT request capabilities the client did not
declare (-32021 otherwise).
| Feature | Spec requirement | wymcp status |
|---|---|---|
MRTR on tools/call (elicitation; sampling/roots are Deprecated features) | Pattern defined | ❌ the planned MRTR change (obsoletes deliver_response on the modern lane) |
notifications/elicitation/complete, elicitationId | Removed | N/A |
4. Utilities
4.1 Removed / deprecated in 2026-07-28
| Legacy feature | Modern fate | wymcp consequence |
|---|---|---|
ping | Removed | Modern lane has no ping; 404 + -32601. Legacy lane keeps it |
initialize / notifications/initialized | Removed (absent from the modern schema) | Legacy-lane-only; era-classification signal |
Sessions / Mcp-Session-Id / DELETE teardown | Removed | Legacy-lane-only; header on a modern-fields request = era-mix, fails closed (wymcp policy) |
| GET standalone SSE stream | Removed → subscriptions/listen | Legacy-lane-only; listen deferred (a subscriptions/listen implementation is planned) |
SSE event IDs / Last-Event-ID replay | Removed; broken stream → client re-issues | Legacy-lane-only |
logging/setLevel | Removed; per-request _meta logLevel opt-in defined — but the whole Logging feature is Deprecated (SEP-2577) | Modern lane implements no logging (wymcp's decision): no capability, no notifications/message; Context.log answers {:error, :not_supported} |
notifications/roots/list_changed | Removed | Never implemented |
| Roots, Sampling, Logging features | Deprecated (≥ 12-month window) | Informs the planned MRTR change's scope (elicitation is the live part) |
notifications/cancelled (client → server) | stdio-only; on HTTP, stream close is the signal | Modern lane: none (no SSE responses in core scope). Legacy lane keeps Methods.Cancelled |
| Tasks (experimental core feature) | Moved to extension io.modelcontextprotocol/tasks (SEP-2663): tasks/get polling, tasks/update, no tasks/list, no blocking tasks/result | ❌ a planned tasks-extension change |
4.2 Progress
progressToken / notifications/progress survive, riding the
originating request's SSE response stream. ❌ out of core scope (no
modern SSE responses; zero consumer usage of report_progress — it
stays :ok, its silent no-op no-session value, on the modern lane).
Becomes real work the day a consumer needs it; a natural companion to
the planned MRTR change's stream machinery.
4.3 Caching (SEP-2549)
| Feature | Spec requirement | wymcp status |
|---|---|---|
ttlMs (int ≥ 0) + cacheScope ("public"/"private") on resultType: "complete" results of server/discover, tools/list (+ prompts/resources ops when they exist) | MUST | ✅ Wymcp.Modern defaults: discover 3_600_000, tools/list 300_000, both "private" |
| TTL is a freshness hint; no revalidation mechanism; stale-if-error allowed | Defined | N/A (client-side) |
"private" caches never shared across authorization contexts | MUST (cache-side) | N/A — but the reason "private" is wymcp's never-wrong default |
4.4 Subscriptions (subscriptions/listen)
Single long-lived POST-response stream for opted-in change
notifications (toolsListChanged, …); server MUST send
notifications/subscriptions/acknowledged first; every stream message
carries io.modelcontextprotocol/subscriptionId; server MUST NOT send
un-requested types. ❌ deferred whole (a subscriptions/listen implementation is planned)
— the modern lane's tool list cannot change within a process, so there is
nothing to signal.
4.5 Extensions
extensions maps on both capability objects (SEP-2133): identifier →
settings, reverse-DNS-prefixed identifiers, revert-to-core-or-reject
negotiation. ❌ no core scope beyond serving an absent field; first real
use is the planned tasks extension.
4.6 OpenTelemetry trace context
_meta conventions for traceparent/tracestate/baggage (SEP-414,
documented, not required). ❌ not in scope; kin to the deferred
rejection-observability work.
5. Summary
The modern era in one paragraph: stateless, POST-only, per-request
protocol fields instead of a handshake, server/discover instead of
initialize, cache hints instead of a notification stream (unless a
client opens subscriptions/listen), MRTR instead of server-initiated
requests, tasks as an extension, and Logging/Sampling/Roots deprecated.
wymcp today: the modern core is live beside the 2025-11-25-only
legacy lane — era classification with the fail-closed era-mix policy,
server/discover, protocol-fields enforcement (-32602/-32022),
resultType: "complete", serverInfo result _meta, cache hints,
plain-JSON responses, and era-tagged telemetry. Planned changes own MRTR,
header binding (-32020, Mcp-Method/Mcp-Name),
subscriptions/listen, the tasks extension, and the eventual legacy
drop (which deletes the legacy lane and its overview doc, ending the
dual-era period).