@@ -435,7 +435,9 @@ def __post_init__(self) -> None:
435435 source = f"{ SPEC_2026_BASE_URL } /basic/versioning#protocol-version-negotiation" ,
436436 behavior = (
437437 "When server/discover returns -32022 UnsupportedProtocolVersion, the client retries once with "
438- "the intersection of error.data.supported and its own modern versions; an empty intersection raises."
438+ "the intersection of error.data.supported and its own modern versions; an empty intersection raises "
439+ "when error.data.supported names no handshake-era version; a list that names one falls back to the "
440+ "initialize handshake."
439441 ),
440442 added_in = "2026-07-28" ,
441443 supersedes = ("lifecycle:version:downgrade" , "lifecycle:version:reject-unsupported" ),
@@ -529,8 +531,9 @@ def __post_init__(self) -> None:
529531 "lifecycle:discover:fallback-method-not-found" : Requirement (
530532 source = f"{ SPEC_2026_BASE_URL } /basic/transports/stdio#backward-compatibility" ,
531533 behavior = (
532- "When server/discover is rejected with a JSON-RPC error other than -32022 (see "
533- "lifecycle:discover:retry-on-32022), or with a bare HTTP 4xx, an auto-negotiating client falls back to "
534+ "When server/discover is rejected with a JSON-RPC error other than -32022 (which retries, raises, or "
535+ "falls back to the handshake according to the supported list; see lifecycle:discover:retry-on-32022), "
536+ "or with a bare HTTP 4xx, an auto-negotiating client falls back to "
534537 "the legacy initialize handshake and connects at a handshake-era version; the fallback is not keyed to "
535538 "specific codes because legacy servers reject the probe with various codes."
536539 ),
@@ -622,11 +625,9 @@ def __post_init__(self) -> None:
622625 "protocol:directionality:no-client-responses" : Requirement (
623626 source = f"{ SPEC_2026_BASE_URL } /basic/patterns" ,
624627 behavior = (
625- "A 2026-07-28 wire trace contains no server-initiated JSON-RPC requests and no "
626- "client-sent JSON-RPC responses: every client-to-server frame is a request and every "
627- "server-to-client frame is a response or a request-scoped notification, never a request, even "
628- "across a multi-round-trip exchange that at 2025-11-25 was a server-initiated request answered "
629- "by the client."
628+ "A 2026-07-28 wire trace contains no server-initiated JSON-RPC requests and no client-sent "
629+ "JSON-RPC responses; notifications flow in both directions. This holds even across a "
630+ "multi-round-trip exchange that at 2025-11-25 was a server-initiated request answered by the client."
630631 ),
631632 added_in = "2026-07-28" ,
632633 note = (
@@ -1337,8 +1338,8 @@ def __post_init__(self) -> None:
13371338 "client:x-mcp-header:invalid-definition-rejected:non-primitive" : Requirement (
13381339 source = f"{ SPEC_2026_BASE_URL } /basic/transports/streamable-http#schema-extension" ,
13391340 behavior = (
1340- "An x-mcp-header annotation on a non-primitive property (e.g. type number, which "
1341- "the spec explicitly forbids) makes the tool definition invalid and the modern "
1341+ "An x-mcp-header annotation is permitted only on integer, string or boolean properties; a tool "
1342+ "that annotates any other type (for example number) has an invalid definition and the modern "
13421343 "client excludes it from tools/list."
13431344 ),
13441345 added_in = "2026-07-28" ,
@@ -1646,11 +1647,26 @@ def __post_init__(self) -> None:
16461647 behavior = "resources/read returns text contents carrying uri, mimeType, and the text." ,
16471648 ),
16481649 "resources:read:unknown-uri" : Requirement (
1650+ source = f"{ SPEC_BASE_URL } /server/resources#error-handling" ,
1651+ behavior = "resources/read for an unknown URI returns JSON-RPC error -32002 (resource not found)." ,
1652+ divergence = Divergence (
1653+ note = (
1654+ "The server answers -32602 (invalid params, URI in error.data) on handshake-era connections too; "
1655+ "when a 2025-era -32002 arm lands, re-pin the 2025 cells to -32002 and delete this divergence."
1656+ ),
1657+ ),
1658+ note = "replaced at 2026-07-28 (SEP-2164): unknown URIs are Invalid params (-32602)." ,
1659+ removed_in = "2026-07-28" ,
1660+ superseded_by = "resources:read:unknown-uri-invalid-params" ,
1661+ ),
1662+ "resources:read:unknown-uri-invalid-params" : Requirement (
16491663 source = f"{ SPEC_2026_BASE_URL } /server/resources#error-handling" ,
16501664 behavior = (
16511665 "resources/read for a URI matching no registered resource returns JSON-RPC error -32602 "
16521666 "(invalid params) with the requested URI in error.data, per SEP-2164."
16531667 ),
1668+ added_in = "2026-07-28" ,
1669+ supersedes = ("resources:read:unknown-uri" ,),
16541670 ),
16551671 "resources:subscribe" : Requirement (
16561672 source = f"{ SPEC_BASE_URL } /server/resources#subscriptions" ,
@@ -1719,14 +1735,14 @@ def __post_init__(self) -> None:
17191735 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/subscriptions#graceful-closure" ,
17201736 behavior = (
17211737 "A server ending a subscription on its own initiative answers the original "
1722- "subscriptions/listen request with an empty result (stamped with the subscriptionId) before "
1723- "closing the stream, so the client can distinguish a graceful close from a transport drop."
1738+ "subscriptions/listen request with an empty result before closing the stream, so the "
1739+ "client can distinguish a graceful close from a transport drop."
17241740 ),
17251741 added_in = "2026-07-28" ,
17261742 note = (
17271743 "The deliberate close is a lowlevel-Server surface: ListenHandler.close() ends every open "
1728- "stream with the stamped empty result; MCPServer registers its ListenHandler by default "
1729- "and exposes no teardown handle."
1744+ "stream with the empty result, which carries the subscriptionId stamp the bound test does not "
1745+ "observe. MCPServer registers its ListenHandler by default and exposes no teardown handle."
17301746 ),
17311747 ),
17321748 "subscriptions:listen:notification-stamped" : Requirement (
@@ -4658,8 +4674,9 @@ def __post_init__(self) -> None:
46584674 added_in = "2026-07-28" ,
46594675 transports = ("streamable-http" ,),
46604676 note = (
4661- "Only observable over streamable HTTP. Scoped to the 2026-07-28 entry, where the SHOULD is new; the "
4662- "legacy SSE and streamable-HTTP transports send no such header and are not bound by this entry."
4677+ "Only observable over streamable HTTP. Scoped to the 2026-07-28 entry, where the SHOULD is stated; "
4678+ "the handshake-era transports also emit the header through their SSE response class but are not "
4679+ "bound by this entry."
46634680 ),
46644681 ),
46654682 "hosting:http:modern:header-name-case-insensitive" : Requirement (
@@ -4954,12 +4971,16 @@ def __post_init__(self) -> None:
49544971 "client-transport:http:body-derived-headers" : Requirement (
49554972 source = f"{ SPEC_2026_BASE_URL } /basic/transports/streamable-http#standard-request-headers" ,
49564973 behavior = (
4957- "An envelope-bearing request body yields MCP-Protocol-Version, Mcp-Method, and (for tools/call) "
4958- "Mcp-Name headers on the outgoing HTTP request; a body without the envelope yields none ."
4974+ "An envelope-bearing request body yields MCP-Protocol-Version, Mcp-Method and (for tools/call) "
4975+ "Mcp-Name headers on the outgoing HTTP request, derived from the body at the transport seam ."
49594976 ),
49604977 added_in = "2026-07-28" ,
49614978 transports = ("streamable-http" ,),
4962- note = "Only observable over streamable HTTP: headers are derived from the body envelope at the transport seam." ,
4979+ note = (
4980+ "Only observable over streamable HTTP. Handshake-era requests carry no envelope; their "
4981+ "MCP-Protocol-Version header is stamped from the negotiated session instead "
4982+ "(client-transport:http:protocol-version-header)."
4983+ ),
49634984 ),
49644985 "client-transport:http:mcp-name-base64-sentinel" : Requirement (
49654986 source = f"{ SPEC_2026_BASE_URL } /basic/transports/streamable-http#standard-request-headers" ,
@@ -5450,16 +5471,15 @@ def __post_init__(self) -> None:
54505471 "client-auth:iss:no-normalize" : Requirement (
54515472 source = f"{ SPEC_2026_BASE_URL } /basic/authorization#authorization-response-validation" ,
54525473 behavior = (
5453- "The iss comparison is simple string comparison (RFC 3986 section 6.2.1): a value "
5454- "differing from the recorded issuer only by a trailing slash is rejected as a "
5455- "mismatch -- no scheme or host case folding, default-port elision, trailing-slash, "
5456- "or percent-encoding normalization is applied before comparison."
5474+ "The iss comparison is a plain string comparison against the recorded issuer (RFC 3986 "
5475+ "section 6.2.1): a callback iss differing only by a trailing slash is rejected as a mismatch."
54575476 ),
54585477 added_in = "2026-07-28" ,
54595478 transports = ("streamable-http" ,),
54605479 note = (
5461- "OAuth is HTTP-only. The comparison is a single string inequality; the test pins the "
5462- "trailing-slash arm as the representative normalization class."
5480+ "OAuth is HTTP-only. The recorded issuer is the parsed metadata URL, so scheme and host case "
5481+ "and default ports are already normalized when the metadata is read; trailing-slash and "
5482+ "percent-encoding differences are not, and the test pins the trailing-slash arm."
54635483 ),
54645484 ),
54655485 "client-auth:iss:supported-missing-reject" : Requirement (
0 commit comments