@@ -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 ),
@@ -2288,17 +2302,22 @@ def __post_init__(self) -> None:
22882302 "requests require sampling.tools; thisServer/allServers context -- itself deprecated -- should "
22892303 "not be used without sampling.context)."
22902304 ),
2305+ divergence = Divergence (
2306+ note = (
2307+ "The embed gate is not implemented: an input_required result carrying a "
2308+ "sampling/createMessage request for a client that declared no sampling capability is "
2309+ "transmitted as-is, and the violation surfaces as the client driver's refusal "
2310+ "(INVALID_REQUEST, 'Sampling not supported') aborting the call. The sub-capability legs "
2311+ "(sampling.tools, sampling.context) are equally ungated and covered by this divergence "
2312+ "without separate pins."
2313+ ),
2314+ ),
22912315 added_in = "2026-07-28" ,
22922316 supersedes = (
22932317 "sampling:create:not-supported" ,
22942318 "sampling:tools:server-gated-by-capability" ,
22952319 "sampling:context:server-gated-by-capability" ,
22962320 ),
2297- deferred = (
2298- "Not implemented in the SDK: the server does not gate input_required input requests against the "
2299- "client's declared capabilities -- a handler can embed a sampling/createMessage request for a "
2300- "client that never declared the matching capability and it is sent as-is."
2301- ),
23022321 ),
23032322 # ═══════════════════════════════════════════════════════════════════════════
23042323 # Elicitation (server → client)
@@ -2668,18 +2687,42 @@ def __post_init__(self) -> None:
26682687 # ═══════════════════════════════════════════════════════════════════════════
26692688 # MRTR (multi-round-trip requests, 2026-07-28)
26702689 # ═══════════════════════════════════════════════════════════════════════════
2690+ "mrtr:input-required-result:at-least-one-of" : Requirement (
2691+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#server-requirements-basic-workflow" ,
2692+ behavior = (
2693+ "An InputRequiredResult carries at least one of inputRequests or requestState; a "
2694+ "handler-built violation fails at construction and surfaces to the client as a JSON-RPC "
2695+ "error, never as a malformed interim result."
2696+ ),
2697+ added_in = "2026-07-28" ,
2698+ note = (
2699+ "The at-least-one-of MUST is enforced by construction (mcp_types model validator). Both "
2700+ "2026 dispatchers map the handler's ValidationError to the shared "
2701+ "ErrorData(INVALID_PARAMS, 'Invalid request parameters', data='') shape "
2702+ "(handler_exception_to_error_data); INTERNAL_ERROR is arguably the more apt code for a "
2703+ "server-side construction bug, but the spec mandates no code for this failure."
2704+ ),
2705+ ),
26712706 "mrtr:input-responses:invalid-rejected" : Requirement (
26722707 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#error-handling" ,
26732708 behavior = (
2674- "The server validates that a retry's inputResponses parse as valid results for the requests it "
2675- "issued; content that violates the requested schema is rejected rather than silently accepted ."
2709+ "The server validates that a retry's inputResponses parse as a valid InputResponses object; "
2710+ "a structurally malformed map is rejected with a JSON-RPC error before the handler runs ."
26762711 ),
26772712 added_in = "2026-07-28" ,
26782713 supersedes = ("elicitation:form:response-validation" ,),
2679- deferred = (
2680- "Not yet covered here: 2026-07-28 successor entry registered by the era pass ahead of its test; "
2681- "the behaviour is implemented and drivable (phase-4 verdict)."
2714+ note = (
2715+ "Elicited content is handed to the handler without requestedSchema re-validation; servers "
2716+ "validate semantic constraints themselves (spec asks only for structural validation)."
2717+ ),
2718+ ),
2719+ "mrtr:input-responses:key-correspondence" : Requirement (
2720+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#inputresponses" ,
2721+ behavior = (
2722+ "A retry's inputResponses map is keyed by the originating inputRequests keys, each value "
2723+ "the client's typed result for that key's request (e.g. ElicitResult, ListRootsResult)."
26822724 ),
2725+ added_in = "2026-07-28" ,
26832726 ),
26842727 "mrtr:url-elicitation:no-32042-on-2026" : Requirement (
26852728 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr" ,
@@ -2695,10 +2738,6 @@ def __post_init__(self) -> None:
26952738 "mcpserver:tool:url-elicitation-error" ,
26962739 "flow:elicitation:url-required-then-retry" ,
26972740 ),
2698- deferred = (
2699- "Not yet covered here: 2026-07-28 successor entry registered by the era pass ahead of its test; "
2700- "the behaviour is implemented and drivable (phase-4 verdict)."
2701- ),
27022741 ),
27032742 "mrtr:tools-call:write-once-roundtrip" : Requirement (
27042743 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#basic-workflow" ,
@@ -2747,10 +2786,41 @@ def __post_init__(self) -> None:
27472786 ),
27482787 added_in = "2026-07-28" ,
27492788 supersedes = ("flow:elicitation:multi-step-form" ,),
2750- deferred = (
2751- "Not yet covered here: 2026-07-28 successor entry registered by the era pass ahead of its test; "
2752- "the behaviour is implemented and drivable (phase-4 verdict)."
2789+ ),
2790+ "mrtr:rounds-cap" : Requirement (
2791+ source = "sdk" ,
2792+ behavior = (
2793+ "Client.call_tool / get_prompt / read_resource bound the input_required retry loop at the "
2794+ "configurable input_required_max_rounds; a server that keeps answering input_required past "
2795+ "the cap raises InputRequiredRoundsExceededError carrying the configured cap."
27532796 ),
2797+ added_in = "2026-07-28" ,
2798+ ),
2799+ "mrtr:push-api:loud-fail-2026" : Requirement (
2800+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr" ,
2801+ behavior = (
2802+ "The push-style server-to-client request APIs (ServerSession.elicit_form / elicit_url / "
2803+ "create_message / list_roots) on a 2026-07-28 connection fail with a typed local error "
2804+ "(NoBackChannelError, INVALID_REQUEST) before any request reaches the client; a handler "
2805+ "can catch it and fall back, and the originating call still completes."
2806+ ),
2807+ divergence = Divergence (
2808+ note = (
2809+ "The prohibition is enforced by each transport's missing back-channel, not by an "
2810+ "era gate on the send path, and the enforcement splits per transport and per leg. "
2811+ "Standalone sends (no related_request_id) raise NoBackChannelError locally on both "
2812+ "2026 transports because the per-request Connection has no outbound channel. "
2813+ "Request-scoped sends (related_request_id=...) ride the per-request dispatch "
2814+ "context, whose can_send_request the modern HTTP entry hard-codes to False but the "
2815+ "in-memory direct-dispatcher pair leaves at its True default -- so in-memory the "
2816+ "forbidden elicitation/create frame IS transmitted, and the failure comes back from "
2817+ "the client's 2026 version gate (METHOD_NOT_FOUND) instead of arising locally. An "
2818+ "era-aware gate on the send path would loud-fail both legs on every transport; when "
2819+ "it lands, re-pin the request-scoped in-memory test to the local NoBackChannelError "
2820+ "and delete this divergence."
2821+ ),
2822+ ),
2823+ added_in = "2026-07-28" ,
27542824 ),
27552825 # ═══════════════════════════════════════════════════════════════════════════
27562826 # Roots (server → client)
@@ -2850,13 +2920,16 @@ def __post_init__(self) -> None:
28502920 "The server does not place a roots/list request in an input_required result's inputRequests "
28512921 "for a client that did not declare the roots capability."
28522922 ),
2923+ divergence = Divergence (
2924+ note = (
2925+ "The embed gate is not implemented: an input_required result carrying a roots/list "
2926+ "request for a client that did not declare the roots capability is transmitted as-is, "
2927+ "and the violation surfaces as the client driver's refusal (INVALID_REQUEST, 'List "
2928+ "roots not supported') aborting the call."
2929+ ),
2930+ ),
28532931 added_in = "2026-07-28" ,
28542932 supersedes = ("roots:list:not-supported" ,),
2855- deferred = (
2856- "Not implemented in the SDK: the server does not gate input_required input requests against the "
2857- "client's declared capabilities -- a handler can embed a roots/list request for a client that "
2858- "never declared the roots capability and it is sent as-is."
2859- ),
28602933 ),
28612934 "roots:uri:file-scheme" : Requirement (
28622935 source = f"{ SPEC_BASE_URL } /client/roots#root" ,
0 commit comments