1616 CLIENT_INFO_META_KEY ,
1717 INVALID_PARAMS ,
1818 INVALID_REQUEST ,
19- METHOD_NOT_FOUND ,
2019 PROTOCOL_VERSION_META_KEY ,
2120 CallToolResult ,
2221 ClientCapabilities ,
@@ -481,8 +480,10 @@ async def call_tool(
481480async def test_push_elicit_on_2026_raises_typed_local_error_and_call_still_completes (connect : Connect ) -> None :
482481 """A push API call on a 2026 connection raises a typed local error and the call still completes.
483482
484- Spec-mandated outcome, incidental enforcement: the gate is "no back-channel", not "wrong era".
485- One push API stands for all four: they share ServerSession.send_request's channel selection.
483+ Spec-mandated outcome, era-routed enforcement: every modern dispatch path installs a
484+ channel-less context by construction, so the gate is "no back-channel", never a send-time
485+ era check. One push API stands for all four: they share ServerSession.send_request's
486+ channel selection.
486487 """
487488 caught : list [NoBackChannelError ] = []
488489
@@ -524,14 +525,16 @@ async def never_deliverable(context: ClientRequestContext, params: types.ElicitR
524525
525526
526527@requirement ("mrtr:push-api:loud-fail-2026" )
527- async def test_request_scoped_push_elicit_on_in_memory_2026_transmits_the_forbidden_frame () -> None :
528- """PINS A KNOWN GAP: a request-scoped push elicit on in-memory 2026 transmits the forbidden frame.
529-
530- The no-back-channel gate is per-transport and the in-memory request-scoped channel still has one,
531- so the failure comes back from the client's 2026 version gate instead of arising locally. When an
532- era-aware send gate lands: re-pin to the local NoBackChannelError and delete the Divergence.
528+ async def test_request_scoped_push_elicit_on_in_memory_2026_loud_fails_locally_and_the_call_still_completes () -> None :
529+ """A request-scoped push elicit on in-memory 2026 loud-fails locally and the call still completes.
530+
531+ The related id routes the send onto the per-request dispatch channel -- the one leg whose
532+ channel is otherwise live in-memory -- so this pin proves local provenance: the typed
533+ NoBackChannelError (never a peer answer) and a callback that never fires. A delivered frame
534+ would raise NotImplementedError in the callback, surface as a non-NoBackChannelError error,
535+ escape the narrowed except, and fail the test loudly.
533536 """
534- caught : list [MCPError ] = []
537+ caught : list [NoBackChannelError ] = []
535538
536539 async def list_tools (
537540 ctx : ServerRequestContext , params : types .PaginatedRequestParams | None
@@ -544,26 +547,32 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara
544547 try :
545548 # The related id routes the send onto the per-request dispatch channel.
546549 await ctx .session .elicit_form ("Need a name" , _NAME_SCHEMA , related_request_id = ctx .request_id )
547- except MCPError as exc :
548- # MCPError, not NoBackChannelError: nothing is raised locally -- the failure is the peer's answer .
550+ except NoBackChannelError as exc :
551+ # Narrow on purpose: a peer-answered MCPError would propagate and fail the test .
549552 caught .append (exc )
550553 return CallToolResult (content = [TextContent (text = "fallback" )])
551554
552555 server = Server ("scoped-push" , on_list_tools = list_tools , on_call_tool = call_tool )
553556
554- # Declares the elicitation capability; the body is itself the never-delivered assertion .
557+ # Registering the callback declares the elicitation capability; it must never fire .
555558 async def never_deliverable (context : ClientRequestContext , params : types .ElicitRequestParams ) -> ElicitResult :
556559 raise NotImplementedError
557560
558561 async with Client (server , mode = LATEST_MODERN_VERSION , elicitation_callback = never_deliverable ) as client :
559562 result = await client .call_tool ("ask" , {})
560563
561- # The connection survives the rejected frame .
564+ # The failed push did not poison the request: the call completes with the handler's fallback .
562565 assert result == snapshot (CallToolResult (content = [TextContent (text = "fallback" )]))
563566 assert len (caught ) == 1
564- # Only the pre-dispatch client version gate answers data=<method>: transmission proven, callback never reached.
567+ assert caught [ 0 ]. method == "elicitation/create"
565568 assert caught [0 ].error == snapshot (
566- ErrorData (code = METHOD_NOT_FOUND , message = "Method not found" , data = "elicitation/create" )
569+ ErrorData (
570+ code = INVALID_REQUEST ,
571+ message = (
572+ "Cannot send 'elicitation/create': this transport context has no back-channel "
573+ "for server-initiated requests."
574+ ),
575+ )
567576 )
568577
569578
0 commit comments