@@ -277,8 +277,10 @@ async def test_input_required_result_with_neither_field_cannot_reach_the_client(
277277 ValidationError to the same SDK-defined invalid-params error, so one snapshot serves both cells.
278278 """
279279
280+ calls : list [str ] = []
281+
280282 async def call_tool (ctx : ServerRequestContext , params : types .CallToolRequestParams ) -> InputRequiredResult :
281- assert params .name == "bare"
283+ calls . append ( params .name )
282284 # Statically legal (both fields default None); raises pydantic's ValidationError here.
283285 return InputRequiredResult ()
284286
@@ -291,6 +293,8 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara
291293 assert exc_info .value .error == snapshot (
292294 ErrorData (code = INVALID_PARAMS , message = "Invalid request parameters" , data = "" )
293295 )
296+ # The handler ran: the error is its ValidationError, not a pre-dispatch params rejection.
297+ assert calls == ["bare" ]
294298
295299
296300@requirement ("mrtr:input-responses:key-correspondence" )
@@ -446,23 +450,30 @@ async def call_tool(
446450
447451
448452@requirement ("mrtr:push-api:loud-fail-2026" )
453+ @pytest .mark .parametrize ("request_scoped" , [False , True ], ids = ["no-related-request-id" , "related-request-id" ])
449454async def test_push_elicit_on_2026_raises_typed_local_error_and_call_still_completes (
450- connect : Connect , unstamped : Unstamp
455+ connect : Connect , unstamped : Unstamp , request_scoped : bool
451456) -> None :
452457 """A push API call on a 2026 connection raises a typed local error and the call still completes.
453458
454459 Spec-mandated outcome, era-routed enforcement: every modern dispatch path installs a
455460 channel-less context by construction, so the gate is "no back-channel", never a send-time
456461 era check. One push API stands for all four: they share ServerSession.send_request's
457- channel selection.
462+ channel selection. The request-scoped variant passes the originating request id, which
463+ routes the send onto the per-request dispatch channel (the one leg otherwise live in
464+ memory), so both channel selections prove local provenance: the typed NoBackChannelError
465+ and a callback that never fires.
458466 """
459467 caught : list [NoBackChannelError ] = []
460468
461469 async def call_tool (ctx : ServerRequestContext , params : types .CallToolRequestParams ) -> CallToolResult :
462470 assert params .name == "ask"
471+ assert ctx .request_id is not None
472+ related_request_id = ctx .request_id if request_scoped else None
463473 try :
464- await ctx .session .elicit_form ("Need a name" , _NAME_SCHEMA )
474+ await ctx .session .elicit_form ("Need a name" , _NAME_SCHEMA , related_request_id = related_request_id )
465475 except NoBackChannelError as exc :
476+ # Narrow on purpose: a peer-answered MCPError would propagate and fail the test.
466477 caught .append (exc )
467478 return CallToolResult (content = [TextContent (text = "fallback" )])
468479
@@ -490,53 +501,6 @@ async def never_deliverable(context: ClientRequestContext, params: types.ElicitR
490501 )
491502
492503
493- @requirement ("mrtr:push-api:loud-fail-2026" )
494- async def test_request_scoped_push_elicit_on_in_memory_2026_loud_fails_locally_and_the_call_still_completes () -> None :
495- """A request-scoped push elicit on in-memory 2026 loud-fails locally and the call still completes.
496-
497- The related id routes the send onto the per-request dispatch channel -- the one leg whose
498- channel is otherwise live in-memory -- so this pin proves local provenance: the typed
499- NoBackChannelError (never a peer answer) and a callback that never fires. A delivered frame
500- would raise NotImplementedError in the callback, surface as a non-NoBackChannelError error,
501- escape the narrowed except, and fail the test loudly.
502- """
503- caught : list [NoBackChannelError ] = []
504-
505- async def call_tool (ctx : ServerRequestContext , params : types .CallToolRequestParams ) -> CallToolResult :
506- assert params .name == "ask"
507- assert ctx .request_id is not None
508- try :
509- # The related id routes the send onto the per-request dispatch channel.
510- await ctx .session .elicit_form ("Need a name" , _NAME_SCHEMA , related_request_id = ctx .request_id )
511- except NoBackChannelError as exc :
512- # Narrow on purpose: a peer-answered MCPError would propagate and fail the test.
513- caught .append (exc )
514- return CallToolResult (content = [TextContent (text = "fallback" )])
515-
516- server = Server ("scoped-push" , on_list_tools = tool_listing ("ask" ), on_call_tool = call_tool )
517-
518- # Registering the callback declares the elicitation capability; it must never fire.
519- async def never_deliverable (context : ClientRequestContext , params : types .ElicitRequestParams ) -> ElicitResult :
520- raise NotImplementedError
521-
522- async with Client (server , mode = LATEST_MODERN_VERSION , elicitation_callback = never_deliverable ) as client :
523- result = await client .call_tool ("ask" , {})
524-
525- # The failed push did not poison the request: the call completes with the handler's fallback.
526- assert strip_stamp (result ) == snapshot (CallToolResult (content = [TextContent (text = "fallback" )]))
527- assert len (caught ) == 1
528- assert caught [0 ].method == "elicitation/create"
529- assert caught [0 ].error == snapshot (
530- ErrorData (
531- code = INVALID_REQUEST ,
532- message = (
533- "Cannot send 'elicitation/create': this transport context has no back-channel "
534- "for server-initiated requests."
535- ),
536- )
537- )
538-
539-
540504@requirement ("sampling:mrtr:capability:not-declared" )
541505async def test_sampling_request_embedded_for_a_non_sampling_client_is_sent_and_refused_client_side (
542506 connect : Connect ,
0 commit comments