@@ -565,6 +565,20 @@ def __post_init__(self) -> None:
565565 "to a request the client sent or a notification carrying no id."
566566 ),
567567 ),
568+ "protocol:directionality:no-client-responses" : Requirement (
569+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns" ,
570+ behavior = (
571+ "A 2026-07-28 wire trace contains no server-initiated JSON-RPC requests and no "
572+ "client-sent JSON-RPC responses: every client-to-server frame is a request and every "
573+ "server-to-client frame is a response, even across a multi-round-trip exchange that at "
574+ "2025-11-25 was a server-initiated request answered by the client."
575+ ),
576+ added_in = "2026-07-28" ,
577+ note = (
578+ "Asserted at the streamable HTTP wire seam: the in-memory 2026 transport dispatches "
579+ "typed objects directly with no JSON-RPC framing, so it has no trace to inspect."
580+ ),
581+ ),
568582 "protocol:cancel:abort-signal" : Requirement (
569583 source = f"{ SPEC_BASE_URL } /basic/utilities/cancellation#cancellation-flow" ,
570584 behavior = (
@@ -1287,7 +1301,7 @@ def __post_init__(self) -> None:
12871301 note = (
12881302 "removed in 2026-07-28 (SEP-2322); in-tool elicitation now returns an input_required result from "
12891303 "the tool; the push Context API's 2026 failure mode is pinned separately by "
1290- "mrtr:push-api:loud-fail-2026 when the mrtr add-batch lands it ."
1304+ "mrtr:push-api:loud-fail-2026."
12911305 ),
12921306 arm_exclusions = (ArmExclusion (reason = "server-initiated-request" , transport = "streamable-http-stateless" ),),
12931307 ),
@@ -2287,17 +2301,22 @@ def __post_init__(self) -> None:
22872301 "requests require sampling.tools; thisServer/allServers context -- itself deprecated -- should "
22882302 "not be used without sampling.context)."
22892303 ),
2304+ divergence = Divergence (
2305+ note = (
2306+ "The embed gate is not implemented: an input_required result carrying a "
2307+ "sampling/createMessage request for a client that declared no sampling capability is "
2308+ "transmitted as-is, and the violation surfaces as the client driver's refusal "
2309+ "(INVALID_REQUEST, 'Sampling not supported') aborting the call. The sub-capability legs "
2310+ "(sampling.tools, sampling.context) are equally ungated and covered by this divergence "
2311+ "without separate pins."
2312+ ),
2313+ ),
22902314 added_in = "2026-07-28" ,
22912315 supersedes = (
22922316 "sampling:create:not-supported" ,
22932317 "sampling:tools:server-gated-by-capability" ,
22942318 "sampling:context:server-gated-by-capability" ,
22952319 ),
2296- deferred = (
2297- "Not implemented in the SDK: the server does not gate input_required input requests against the "
2298- "client's declared capabilities -- a handler can embed a sampling/createMessage request for a "
2299- "client that never declared the matching capability and it is sent as-is."
2300- ),
23012320 ),
23022321 # ═══════════════════════════════════════════════════════════════════════════
23032322 # Elicitation (server → client)
@@ -2667,18 +2686,42 @@ def __post_init__(self) -> None:
26672686 # ═══════════════════════════════════════════════════════════════════════════
26682687 # MRTR (multi-round-trip requests, 2026-07-28)
26692688 # ═══════════════════════════════════════════════════════════════════════════
2689+ "mrtr:input-required-result:at-least-one-of" : Requirement (
2690+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#server-requirements-basic-workflow" ,
2691+ behavior = (
2692+ "An InputRequiredResult carries at least one of inputRequests or requestState; a "
2693+ "handler-built violation fails at construction and surfaces to the client as a JSON-RPC "
2694+ "error, never as a malformed interim result."
2695+ ),
2696+ added_in = "2026-07-28" ,
2697+ note = (
2698+ "The at-least-one-of MUST is enforced by construction (mcp_types model validator). Both "
2699+ "2026 dispatchers map the handler's ValidationError to the shared "
2700+ "ErrorData(INVALID_PARAMS, 'Invalid request parameters', data='') shape "
2701+ "(handler_exception_to_error_data); INTERNAL_ERROR is arguably the more apt code for a "
2702+ "server-side construction bug, but the spec mandates no code for this failure."
2703+ ),
2704+ ),
26702705 "mrtr:input-responses:invalid-rejected" : Requirement (
26712706 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#error-handling" ,
26722707 behavior = (
2673- "The server validates that a retry's inputResponses parse as valid results for the requests it "
2674- "issued; content that violates the requested schema is rejected rather than silently accepted ."
2708+ "The server validates that a retry's inputResponses parse as a valid InputResponses object; "
2709+ "a structurally malformed map is rejected with a JSON-RPC error before the handler runs ."
26752710 ),
26762711 added_in = "2026-07-28" ,
26772712 supersedes = ("elicitation:form:response-validation" ,),
2678- deferred = (
2679- "Not yet covered here: 2026-07-28 successor entry registered by the era pass ahead of its test; "
2680- "the behaviour is implemented and drivable (phase-4 verdict)."
2713+ note = (
2714+ "Elicited content is handed to the handler without requestedSchema re-validation; servers "
2715+ "validate semantic constraints themselves (spec asks only for structural validation)."
2716+ ),
2717+ ),
2718+ "mrtr:input-responses:key-correspondence" : Requirement (
2719+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#inputresponses" ,
2720+ behavior = (
2721+ "A retry's inputResponses map is keyed by the originating inputRequests keys, each value "
2722+ "the client's typed result for that key's request (e.g. ElicitResult, ListRootsResult)."
26812723 ),
2724+ added_in = "2026-07-28" ,
26822725 ),
26832726 "mrtr:url-elicitation:no-32042-on-2026" : Requirement (
26842727 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr" ,
@@ -2694,10 +2737,6 @@ def __post_init__(self) -> None:
26942737 "mcpserver:tool:url-elicitation-error" ,
26952738 "flow:elicitation:url-required-then-retry" ,
26962739 ),
2697- deferred = (
2698- "Not yet covered here: 2026-07-28 successor entry registered by the era pass ahead of its test; "
2699- "the behaviour is implemented and drivable (phase-4 verdict)."
2700- ),
27012740 ),
27022741 "mrtr:tools-call:write-once-roundtrip" : Requirement (
27032742 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#basic-workflow" ,
@@ -2746,10 +2785,41 @@ def __post_init__(self) -> None:
27462785 ),
27472786 added_in = "2026-07-28" ,
27482787 supersedes = ("flow:elicitation:multi-step-form" ,),
2749- deferred = (
2750- "Not yet covered here: 2026-07-28 successor entry registered by the era pass ahead of its test; "
2751- "the behaviour is implemented and drivable (phase-4 verdict)."
2788+ ),
2789+ "mrtr:rounds-cap" : Requirement (
2790+ source = "sdk" ,
2791+ behavior = (
2792+ "Client.call_tool / get_prompt / read_resource bound the input_required retry loop at the "
2793+ "configurable input_required_max_rounds; a server that keeps answering input_required past "
2794+ "the cap raises InputRequiredRoundsExceededError carrying the configured cap."
27522795 ),
2796+ added_in = "2026-07-28" ,
2797+ ),
2798+ "mrtr:push-api:loud-fail-2026" : Requirement (
2799+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr" ,
2800+ behavior = (
2801+ "The push-style server-to-client request APIs (ServerSession.elicit_form / elicit_url / "
2802+ "create_message / list_roots) on a 2026-07-28 connection fail with a typed local error "
2803+ "(NoBackChannelError, INVALID_REQUEST) before any request reaches the client; a handler "
2804+ "can catch it and fall back, and the originating call still completes."
2805+ ),
2806+ divergence = Divergence (
2807+ note = (
2808+ "The prohibition is enforced by each transport's missing back-channel, not by an "
2809+ "era gate on the send path, and the enforcement splits per transport and per leg. "
2810+ "Standalone sends (no related_request_id) raise NoBackChannelError locally on both "
2811+ "2026 transports because the per-request Connection has no outbound channel. "
2812+ "Request-scoped sends (related_request_id=...) ride the per-request dispatch "
2813+ "context, whose can_send_request the modern HTTP entry hard-codes to False but the "
2814+ "in-memory direct-dispatcher pair leaves at its True default -- so in-memory the "
2815+ "forbidden elicitation/create frame IS transmitted, and the failure comes back from "
2816+ "the client's 2026 version gate (METHOD_NOT_FOUND) instead of arising locally. An "
2817+ "era-aware gate on the send path would loud-fail both legs on every transport; when "
2818+ "it lands, re-pin the request-scoped in-memory test to the local NoBackChannelError "
2819+ "and delete this divergence."
2820+ ),
2821+ ),
2822+ added_in = "2026-07-28" ,
27532823 ),
27542824 # ═══════════════════════════════════════════════════════════════════════════
27552825 # Roots (server → client)
@@ -2849,13 +2919,16 @@ def __post_init__(self) -> None:
28492919 "The server does not place a roots/list request in an input_required result's inputRequests "
28502920 "for a client that did not declare the roots capability."
28512921 ),
2922+ divergence = Divergence (
2923+ note = (
2924+ "The embed gate is not implemented: an input_required result carrying a roots/list "
2925+ "request for a client that did not declare the roots capability is transmitted as-is, "
2926+ "and the violation surfaces as the client driver's refusal (INVALID_REQUEST, 'List "
2927+ "roots not supported') aborting the call."
2928+ ),
2929+ ),
28522930 added_in = "2026-07-28" ,
28532931 supersedes = ("roots:list:not-supported" ,),
2854- deferred = (
2855- "Not implemented in the SDK: the server does not gate input_required input requests against the "
2856- "client's declared capabilities -- a handler can embed a roots/list request for a client that "
2857- "never declared the roots capability and it is sent as-is."
2858- ),
28592932 ),
28602933 "roots:uri:file-scheme" : Requirement (
28612934 source = f"{ SPEC_BASE_URL } /client/roots#root" ,
0 commit comments