@@ -551,6 +551,20 @@ def __post_init__(self) -> None:
551551 "to a request the client sent or a notification carrying no id."
552552 ),
553553 ),
554+ "protocol:directionality:no-client-responses" : Requirement (
555+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns" ,
556+ behavior = (
557+ "A 2026-07-28 wire trace contains no server-initiated JSON-RPC requests and no "
558+ "client-sent JSON-RPC responses: every client-to-server frame is a request and every "
559+ "server-to-client frame is a response, even across a multi-round-trip exchange that at "
560+ "2025-11-25 was a server-initiated request answered by the client."
561+ ),
562+ added_in = "2026-07-28" ,
563+ note = (
564+ "Asserted at the streamable HTTP wire seam: the in-memory 2026 transport dispatches "
565+ "typed objects directly with no JSON-RPC framing, so it has no trace to inspect."
566+ ),
567+ ),
554568 "protocol:cancel:abort-signal" : Requirement (
555569 source = f"{ SPEC_BASE_URL } /basic/utilities/cancellation#cancellation-flow" ,
556570 behavior = (
@@ -1250,7 +1264,7 @@ def __post_init__(self) -> None:
12501264 note = (
12511265 "removed in 2026-07-28 (SEP-2322); in-tool elicitation now returns an input_required result from "
12521266 "the tool; the push Context API's 2026 failure mode is pinned separately by "
1253- "mrtr:push-api:loud-fail-2026 when the mrtr add-batch lands it ."
1267+ "mrtr:push-api:loud-fail-2026."
12541268 ),
12551269 arm_exclusions = (ArmExclusion (reason = "server-initiated-request" , transport = "streamable-http-stateless" ),),
12561270 ),
@@ -2197,17 +2211,22 @@ def __post_init__(self) -> None:
21972211 "requests require sampling.tools; thisServer/allServers context -- itself deprecated -- should "
21982212 "not be used without sampling.context)."
21992213 ),
2214+ divergence = Divergence (
2215+ note = (
2216+ "The embed gate is not implemented: an input_required result carrying a "
2217+ "sampling/createMessage request for a client that declared no sampling capability is "
2218+ "transmitted as-is, and the violation surfaces as the client driver's refusal "
2219+ "(INVALID_REQUEST, 'Sampling not supported') aborting the call. The sub-capability legs "
2220+ "(sampling.tools, sampling.context) are equally ungated and covered by this divergence "
2221+ "without separate pins."
2222+ ),
2223+ ),
22002224 added_in = "2026-07-28" ,
22012225 supersedes = (
22022226 "sampling:create:not-supported" ,
22032227 "sampling:tools:server-gated-by-capability" ,
22042228 "sampling:context:server-gated-by-capability" ,
22052229 ),
2206- deferred = (
2207- "Not implemented in the SDK: the server does not gate input_required input requests against the "
2208- "client's declared capabilities -- a handler can embed a sampling/createMessage request for a "
2209- "client that never declared the matching capability and it is sent as-is."
2210- ),
22112230 ),
22122231 # ═══════════════════════════════════════════════════════════════════════════
22132232 # Elicitation (server → client)
@@ -2577,18 +2596,42 @@ def __post_init__(self) -> None:
25772596 # ═══════════════════════════════════════════════════════════════════════════
25782597 # MRTR (multi-round-trip requests, 2026-07-28)
25792598 # ═══════════════════════════════════════════════════════════════════════════
2599+ "mrtr:input-required-result:at-least-one-of" : Requirement (
2600+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#server-requirements-basic-workflow" ,
2601+ behavior = (
2602+ "An InputRequiredResult carries at least one of inputRequests or requestState; a "
2603+ "handler-built violation fails at construction and surfaces to the client as a JSON-RPC "
2604+ "error, never as a malformed interim result."
2605+ ),
2606+ added_in = "2026-07-28" ,
2607+ note = (
2608+ "The at-least-one-of MUST is enforced by construction (mcp_types model validator). Both "
2609+ "2026 dispatchers map the handler's ValidationError to the shared "
2610+ "ErrorData(INVALID_PARAMS, 'Invalid request parameters', data='') shape "
2611+ "(handler_exception_to_error_data); INTERNAL_ERROR is arguably the more apt code for a "
2612+ "server-side construction bug, but the spec mandates no code for this failure."
2613+ ),
2614+ ),
25802615 "mrtr:input-responses:invalid-rejected" : Requirement (
25812616 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#error-handling" ,
25822617 behavior = (
2583- "The server validates that a retry's inputResponses parse as valid results for the requests it "
2584- "issued; content that violates the requested schema is rejected rather than silently accepted ."
2618+ "The server validates that a retry's inputResponses parse as a valid InputResponses object; "
2619+ "a structurally malformed map is rejected with a JSON-RPC error before the handler runs ."
25852620 ),
25862621 added_in = "2026-07-28" ,
25872622 supersedes = ("elicitation:form:response-validation" ,),
2588- deferred = (
2589- "Not yet covered here: 2026-07-28 successor entry registered by the era pass ahead of its test; "
2590- "the behaviour is implemented and drivable (phase-4 verdict)."
2623+ note = (
2624+ "Elicited content is handed to the handler without requestedSchema re-validation; servers "
2625+ "validate semantic constraints themselves (spec asks only for structural validation)."
2626+ ),
2627+ ),
2628+ "mrtr:input-responses:key-correspondence" : Requirement (
2629+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#inputresponses" ,
2630+ behavior = (
2631+ "A retry's inputResponses map is keyed by the originating inputRequests keys, each value "
2632+ "the client's typed result for that key's request (e.g. ElicitResult, ListRootsResult)."
25912633 ),
2634+ added_in = "2026-07-28" ,
25922635 ),
25932636 "mrtr:url-elicitation:no-32042-on-2026" : Requirement (
25942637 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr" ,
@@ -2604,10 +2647,6 @@ def __post_init__(self) -> None:
26042647 "mcpserver:tool:url-elicitation-error" ,
26052648 "flow:elicitation:url-required-then-retry" ,
26062649 ),
2607- deferred = (
2608- "Not yet covered here: 2026-07-28 successor entry registered by the era pass ahead of its test; "
2609- "the behaviour is implemented and drivable (phase-4 verdict)."
2610- ),
26112650 ),
26122651 "mrtr:tools-call:write-once-roundtrip" : Requirement (
26132652 source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr#basic-workflow" ,
@@ -2656,10 +2695,41 @@ def __post_init__(self) -> None:
26562695 ),
26572696 added_in = "2026-07-28" ,
26582697 supersedes = ("flow:elicitation:multi-step-form" ,),
2659- deferred = (
2660- "Not yet covered here: 2026-07-28 successor entry registered by the era pass ahead of its test; "
2661- "the behaviour is implemented and drivable (phase-4 verdict)."
2698+ ),
2699+ "mrtr:rounds-cap" : Requirement (
2700+ source = "sdk" ,
2701+ behavior = (
2702+ "Client.call_tool / get_prompt / read_resource bound the input_required retry loop at the "
2703+ "configurable input_required_max_rounds; a server that keeps answering input_required past "
2704+ "the cap raises InputRequiredRoundsExceededError carrying the configured cap."
26622705 ),
2706+ added_in = "2026-07-28" ,
2707+ ),
2708+ "mrtr:push-api:loud-fail-2026" : Requirement (
2709+ source = f"{ SPEC_2026_BASE_URL } /basic/patterns/mrtr" ,
2710+ behavior = (
2711+ "The push-style server-to-client request APIs (ServerSession.elicit_form / elicit_url / "
2712+ "create_message / list_roots) on a 2026-07-28 connection fail with a typed local error "
2713+ "(NoBackChannelError, INVALID_REQUEST) before any request reaches the client; a handler "
2714+ "can catch it and fall back, and the originating call still completes."
2715+ ),
2716+ divergence = Divergence (
2717+ note = (
2718+ "The prohibition is enforced by each transport's missing back-channel, not by an "
2719+ "era gate on the send path, and the enforcement splits per transport and per leg. "
2720+ "Standalone sends (no related_request_id) raise NoBackChannelError locally on both "
2721+ "2026 transports because the per-request Connection has no outbound channel. "
2722+ "Request-scoped sends (related_request_id=...) ride the per-request dispatch "
2723+ "context, whose can_send_request the modern HTTP entry hard-codes to False but the "
2724+ "in-memory direct-dispatcher pair leaves at its True default -- so in-memory the "
2725+ "forbidden elicitation/create frame IS transmitted, and the failure comes back from "
2726+ "the client's 2026 version gate (METHOD_NOT_FOUND) instead of arising locally. An "
2727+ "era-aware gate on the send path would loud-fail both legs on every transport; when "
2728+ "it lands, re-pin the request-scoped in-memory test to the local NoBackChannelError "
2729+ "and delete this divergence."
2730+ ),
2731+ ),
2732+ added_in = "2026-07-28" ,
26632733 ),
26642734 # ═══════════════════════════════════════════════════════════════════════════
26652735 # Roots (server → client)
@@ -2759,13 +2829,16 @@ def __post_init__(self) -> None:
27592829 "The server does not place a roots/list request in an input_required result's inputRequests "
27602830 "for a client that did not declare the roots capability."
27612831 ),
2832+ divergence = Divergence (
2833+ note = (
2834+ "The embed gate is not implemented: an input_required result carrying a roots/list "
2835+ "request for a client that did not declare the roots capability is transmitted as-is, "
2836+ "and the violation surfaces as the client driver's refusal (INVALID_REQUEST, 'List "
2837+ "roots not supported') aborting the call."
2838+ ),
2839+ ),
27622840 added_in = "2026-07-28" ,
27632841 supersedes = ("roots:list:not-supported" ,),
2764- deferred = (
2765- "Not implemented in the SDK: the server does not gate input_required input requests against the "
2766- "client's declared capabilities -- a handler can embed a roots/list request for a client that "
2767- "never declared the roots capability and it is sent as-is."
2768- ),
27692842 ),
27702843 "roots:uri:file-scheme" : Requirement (
27712844 source = f"{ SPEC_BASE_URL } /client/roots#root" ,
0 commit comments