MCP 2026-07-28 Spec
View SourcePurpose
Map every feature in the MCP 2026-07-28 specification against what wymcp implements, to guide planning. This revision is the modern era of MCP, and the one wymcp speaks; the spec's versioning page defines the eras. Vocabulary: protocol fields, MRTR.
Status column: the core is implemented; scope notes name the planned change that owns each remaining gap.
1. Base Protocol
1.1 The stateless model
This revision 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.ProtocolFields enforces both on every request |
Reject missing protocol fields with -32602 + HTTP 400 | MUST | ✅ Wymcp.Plugs.ProtocolFields (error.message names the missing field and says what to send) |
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 | ✅ both values live: "complete" on a finished result, "input_required" on one still waiting for the user (Wymcp.Modern) |
Unknown method → HTTP 404 + -32601 | MUST | ✅ the dispatch table (ping, logging/setLevel and notifications/cancelled sent as requests land here) |
Error-code allocation: -32020..-32099 spec-reserved, -32000..-32019 implementation-defined | MUST NOT misallocate | ✅ -32022 allocated; nothing in the implementation-defined range |
1.2 Version negotiation
| 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; supportedVersions is ["2026-07-28"] |
UnsupportedProtocolVersionError -32022 + 400 with data.{supported, requested} | MUST | ✅ Wymcp.Plugs.ProtocolFields; data.requested is omitted where the request offered no version string |
MissingRequiredClientCapabilityError -32021 + 400 with data.requiredCapabilities | MUST | N/A — wymcp requires no client capability: an undeclared elicitation answers the tool's own fallback, never a rejection |
A server speaking this revision alone names its supported versions in any error it returns to an initialize | SHOULD | ✅ Wymcp.Plugs.ProtocolFields answers an initialize -32022 with data.supported, before any field is read |
| Dual-era serving on one endpoint | MAY | N/A by decision — wymcp speaks this revision only; earlier revisions are answered, never served |
| Era determination cached per origin | Client-side | N/A |
1.3 Streamable HTTP transport
| Feature | Spec requirement | wymcp status |
|---|---|---|
| Single endpoint, POST-only | MUST | ✅ POST is the one served verb; every other verb answers 405 with Allow: POST from the fallthrough |
Response: application/json or SSE response stream | MUST offer one; client MUST accept both | ✅ answers plain JSON (no SSE responses — no progress/logging in core scope) |
SSE event IDs / Last-Event-ID resumption | Removed; ignore the header | ✅ the header is ignored; wymcp opens no stream |
An Mcp-Session-Id header from a client of an earlier revision | Ignore it; mint and echo no session id | ✅ ignored; no session id is ever minted |
| Closing the SSE response stream = cancellation | MUST treat disconnect as cancellation | N/A in core scope (no SSE responses) |
Origin validation | MUST | ✅ origin check — a wire check, run before the body is read |
| Auth per request | Stateless model | ✅ auth check — a wire check |
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 | ✅ the header-binding check on every request (Wymcp.Plugs.HeaderBinding); duplicates are the singleton-header check's. A mirror is required only where the body value has a header spelling (header binding) |
| Notification POSTs → 202 | MUST | ✅ the acceptance (the 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 the mount tools, whose definitions are built at the registration moment |
tools/call | MUST if capability declared | ✅ a per-request Context, no state between calls |
Unknown tool name → -32602 (InvalidParamsError) | Defined | ✅ |
| Tool set MUST NOT vary per-connection (MAY vary by authorization) | MUST | ✅ holds by construction: the mount list is fixed at a mount module's compile |
Deterministic tools/list order | SHOULD | ✅ by construction (mount-list 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 — wymcp declares no listChanged; TTL-only freshness (spec-sanctioned); a subscriptions/listen implementation is planned |
Pagination (cursor / nextCursor) | SHOULD | ❌ |
inputSchema/outputSchema loosened to full JSON Schema 2020-12; $ref no network deref; composition bounds | MUST (bounds SHOULD) | ✅ JSV validates full 2020-12 drafts (output schemas and protocol roots — a generated tool's inputSchema is not read for argument validation: arguments are checked by hand plus dispatch gates, and help derives its own gates from its hand-written schema) and wymcp-generated schemas are simple |
structuredContent may be any JSON value | Defined | ❌ core (wymcp assumes an object) |
x-mcp-header argument annotations | MAY (invalid ⇒ client drops tool) | ✅ the generated schema annotates action; a hand-written schema's own annotations are validated at the registration moment and honoured by the header-binding check |
2.2 Resources, Prompts, Completion
Unimplemented in wymcp. When they arrive: results are cacheable
(ttlMs/cacheScope MUST on the five list/read operations),
subscriptions ride subscriptions/listen, resource-not-found is -32602.
3. Server-initiated interaction (MRTR)
Server→client JSON-RPC requests are forbidden in this revision. 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 | ✅ elicitation, form mode: Wymcp.Context.elicit/4 answers from the responses the client threaded back, or ends the run and the call answers input_required. A tool therefore runs once per round — the re-execution model, defined in Wymcp.Context — and requestState carries the prior answers with no integrity envelope, under the spec's MAY-omit clause. Sampling is not implemented; roots never was |
notifications/elicitation/complete, elicitationId | Removed | N/A |
4. Utilities
4.1 Removed / deprecated in 2026-07-28
| Feature of the earlier revisions | Fate in this revision | What wymcp answers |
|---|---|---|
ping | Removed | 404 + -32601, an unknown method |
initialize / notifications/initialized | Removed (absent from the schema) | initialize as a request: 400 + -32022 naming the served version; as a notification, and notifications/initialized: 202, the acceptance |
Sessions / Mcp-Session-Id / DELETE teardown | Removed | the header is ignored and no session id is ever minted; DELETE: 405 with Allow: POST |
| GET standalone SSE stream | Removed → subscriptions/listen | GET: 405 with Allow: POST; listen deferred (a subscriptions/listen implementation is planned) |
SSE event IDs / Last-Event-ID replay | Removed; broken stream → client re-issues | the header is ignored |
logging/setLevel | Removed; per-request _meta logLevel opt-in defined — but the whole Logging feature is Deprecated (SEP-2577) | 404 + -32601; wymcp implements no logging: no capability, no notifications/message |
notifications/roots/list_changed | Removed | 202, the acceptance; never acted on |
| Roots, Sampling, Logging features | Deprecated (≥ 12-month window) | none implemented. Elicitation is the live part, served over MRTR |
notifications/cancelled (client → server) | stdio-only; on HTTP, stream close is the signal | as a notification: 202, the acceptance; as a request: 404 + -32601 |
| 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 SSE
responses; zero consumer usage). Becomes real work the day a consumer
needs it.
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 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. The rejection observability this
was once deferred alongside has shipped — one event per rejection carrying
which rejecter refused the request and why, plus a shipped handler that renders
it — and trace-context propagation is a separate question it leaves open.
5. Summary
This revision 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 core is live and it is the whole server —
server/discover, protocol-fields enforcement (-32602/-32022, the
latter also the answer to an initialize), resultType on every result
— "complete" on a finished one and "input_required" on one still
waiting for the user — serverInfo result _meta, cache hints, plain-JSON
responses, elicitation over MRTR, and header binding (-32020,
Mcp-Method/Mcp-Name, Mcp-Param-*). Every other verb answers 405,
and a stray session or resumption header is ignored. Planned changes own
subscriptions/listen and the tasks extension.