@@ -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 = (
@@ -1285,7 +1299,7 @@ def __post_init__(self) -> None:
12851299 note = (
12861300 "removed in 2026-07-28 (SEP-2322); in-tool elicitation now returns an input_required result from "
12871301 "the tool; the push Context API's 2026 failure mode is pinned separately by "
1288- "mrtr:push-api:loud-fail-2026 when the mrtr add-batch lands it ."
1302+ "mrtr:push-api:loud-fail-2026."
12891303 ),
12901304 arm_exclusions = (ArmExclusion (reason = "server-initiated-request" , transport = "streamable-http-stateless" ),),
12911305 ),
@@ -2285,17 +2299,22 @@ def __post_init__(self) -> None:
22852299 "requests require sampling.tools; thisServer/allServers context -- itself deprecated -- should "
22862300 "not be used without sampling.context)."
22872301 ),
2302+ divergence = Divergence (
2303+ note = (
2304+ "The embed gate is not implemented: an input_required result carrying a "
2305+ "sampling/createMessage request for a client that declared no sampling capability is "
2306+ "transmitted as-is, and the violation surfaces as the client driver's refusal "
2307+ "(INVALID_REQUEST, 'Sampling not supported') aborting the call. The sub-capability legs "
2308+ "(sampling.tools, sampling.context) are equally ungated and covered by this divergence "
2309+ "without separate pins."
2310+ ),
2311+ ),
22882312 added_in = "2026-07-28" ,
22892313 supersedes = (
22902314 "sampling:create:not-supported" ,
22912315 "sampling:tools:server-gated-by-capability" ,
22922316 "sampling:context:server-gated-by-capability" ,
22932317 ),
2294- deferred = (
2295- "Not implemented in the SDK: the server does not gate input_required input requests against the "
2296- "client's declared capabilities -- a handler can embed a sampling/createMessage request for a "
2297- "client that never declared the matching capability and it is sent as-is."
2298- ),
22992318 ),
23002319 # ═══════════════════════════════════════════════════════════════════════════
23012320 # Elicitation (server → client)
@@ -2665,18 +2684,42 @@ def __post_init__(self) -> None:
26652684 # ═══════════════════════════════════════════════════════════════════════════
26662685 # MRTR (multi-round-trip requests, 2026-07-28)
26672686 # ═══════════════════════════════════════════════════════════════════════════
2687+ "mrtr:input-required-result:at-least-one-of" : Requirement (
2688+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#server-requirements-basic-workflow" ,
2689+ behavior = (
2690+ "An InputRequiredResult carries at least one of inputRequests or requestState; a "
2691+ "handler-built violation fails at construction and surfaces to the client as a JSON-RPC "
2692+ "error, never as a malformed interim result."
2693+ ),
2694+ added_in = "2026-07-28" ,
2695+ note = (
2696+ "The at-least-one-of MUST is enforced by construction (mcp_types model validator). Both "
2697+ "2026 dispatchers map the handler's ValidationError to the shared "
2698+ "ErrorData(INVALID_PARAMS, 'Invalid request parameters', data='') shape "
2699+ "(handler_exception_to_error_data); INTERNAL_ERROR is arguably the more apt code for a "
2700+ "server-side construction bug, but the spec mandates no code for this failure."
2701+ ),
2702+ ),
26682703 "mrtr:input-responses:invalid-rejected" : Requirement (
26692704 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#error-handling" ,
26702705 behavior = (
2671- "The server validates that a retry's inputResponses parse as valid results for the requests it "
2672- "issued; content that violates the requested schema is rejected rather than silently accepted ."
2706+ "The server validates that a retry's inputResponses parse as a valid InputResponses object; "
2707+ "a structurally malformed map is rejected with a JSON-RPC error before the handler runs ."
26732708 ),
26742709 added_in = "2026-07-28" ,
26752710 supersedes = ("elicitation:form:response-validation" ,),
2676- deferred = (
2677- "Not yet covered here: 2026-07-28 successor entry registered by the era pass ahead of its test; "
2678- "the behaviour is implemented and drivable (phase-4 verdict)."
2711+ note = (
2712+ "Elicited content is handed to the handler without requestedSchema re-validation; servers "
2713+ "validate semantic constraints themselves (spec asks only for structural validation)."
2714+ ),
2715+ ),
2716+ "mrtr:input-responses:key-correspondence" : Requirement (
2717+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#inputresponses" ,
2718+ behavior = (
2719+ "A retry's inputResponses map is keyed by the originating inputRequests keys, each value "
2720+ "the client's typed result for that key's request (e.g. ElicitResult, ListRootsResult)."
26792721 ),
2722+ added_in = "2026-07-28" ,
26802723 ),
26812724 "mrtr:url-elicitation:no-32042-on-2026" : Requirement (
26822725 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr" ,
@@ -2692,10 +2735,6 @@ def __post_init__(self) -> None:
26922735 "mcpserver:tool:url-elicitation-error" ,
26932736 "flow:elicitation:url-required-then-retry" ,
26942737 ),
2695- deferred = (
2696- "Not yet covered here: 2026-07-28 successor entry registered by the era pass ahead of its test; "
2697- "the behaviour is implemented and drivable (phase-4 verdict)."
2698- ),
26992738 ),
27002739 "mrtr:tools-call:write-once-roundtrip" : Requirement (
27012740 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#basic-workflow" ,
@@ -2744,10 +2783,41 @@ def __post_init__(self) -> None:
27442783 ),
27452784 added_in = "2026-07-28" ,
27462785 supersedes = ("flow:elicitation:multi-step-form" ,),
2747- deferred = (
2748- "Not yet covered here: 2026-07-28 successor entry registered by the era pass ahead of its test; "
2749- "the behaviour is implemented and drivable (phase-4 verdict)."
2786+ ),
2787+ "mrtr:rounds-cap" : Requirement (
2788+ source = "sdk" ,
2789+ behavior = (
2790+ "Client.call_tool / get_prompt / read_resource bound the input_required retry loop at the "
2791+ "configurable input_required_max_rounds; a server that keeps answering input_required past "
2792+ "the cap raises InputRequiredRoundsExceededError carrying the configured cap."
27502793 ),
2794+ added_in = "2026-07-28" ,
2795+ ),
2796+ "mrtr:push-api:loud-fail-2026" : Requirement (
2797+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr" ,
2798+ behavior = (
2799+ "The push-style server-to-client request APIs (ServerSession.elicit_form / elicit_url / "
2800+ "create_message / list_roots) on a 2026-07-28 connection fail with a typed local error "
2801+ "(NoBackChannelError, INVALID_REQUEST) before any request reaches the client; a handler "
2802+ "can catch it and fall back, and the originating call still completes."
2803+ ),
2804+ divergence = Divergence (
2805+ note = (
2806+ "The prohibition is enforced by each transport's missing back-channel, not by an "
2807+ "era gate on the send path, and the enforcement splits per transport and per leg. "
2808+ "Standalone sends (no related_request_id) raise NoBackChannelError locally on both "
2809+ "2026 transports because the per-request Connection has no outbound channel. "
2810+ "Request-scoped sends (related_request_id=...) ride the per-request dispatch "
2811+ "context, whose can_send_request the modern HTTP entry hard-codes to False but the "
2812+ "in-memory direct-dispatcher pair leaves at its True default -- so in-memory the "
2813+ "forbidden elicitation/create frame IS transmitted, and the failure comes back from "
2814+ "the client's 2026 version gate (METHOD_NOT_FOUND) instead of arising locally. An "
2815+ "era-aware gate on the send path would loud-fail both legs on every transport; when "
2816+ "it lands, re-pin the request-scoped in-memory test to the local NoBackChannelError "
2817+ "and delete this divergence."
2818+ ),
2819+ ),
2820+ added_in = "2026-07-28" ,
27512821 ),
27522822 # ═══════════════════════════════════════════════════════════════════════════
27532823 # Roots (server → client)
@@ -2847,13 +2917,16 @@ def __post_init__(self) -> None:
28472917 "The server does not place a roots/list request in an input_required result's inputRequests "
28482918 "for a client that did not declare the roots capability."
28492919 ),
2920+ divergence = Divergence (
2921+ note = (
2922+ "The embed gate is not implemented: an input_required result carrying a roots/list "
2923+ "request for a client that did not declare the roots capability is transmitted as-is, "
2924+ "and the violation surfaces as the client driver's refusal (INVALID_REQUEST, 'List "
2925+ "roots not supported') aborting the call."
2926+ ),
2927+ ),
28502928 added_in = "2026-07-28" ,
28512929 supersedes = ("roots:list:not-supported" ,),
2852- deferred = (
2853- "Not implemented in the SDK: the server does not gate input_required input requests against the "
2854- "client's declared capabilities -- a handler can embed a roots/list request for a client that "
2855- "never declared the roots capability and it is sent as-is."
2856- ),
28572930 ),
28582931 "roots:uri:file-scheme" : Requirement (
28592932 source = f"{ SPEC_BASE_URL } /client/roots#root" ,
0 commit comments