Relax monolith ElicitRequestURLParams.elicitation_id for 2026-07-28 (#2913)

This commit is contained in:
Max
2026-06-19 16:06:08 +01:00
committed by GitHub
parent 510832aa45
commit 1cec2d60f4
3 changed files with 34 additions and 4 deletions
+6 -3
View File
@@ -1868,7 +1868,9 @@ class CancelledNotificationParams(NotificationParams):
The ID of the request to cancel.
This MUST correspond to the ID of a request previously issued in the same direction.
Required on the wire through 2025-06-18; optional from 2025-11-25.
Required on the wire through 2025-06-18; optional at 2025-11-25; required again from
2026-07-28, where it must name a request the client previously issued (servers send
this notification only to terminate a `subscriptions/listen` stream).
"""
reason: str | None = None
"""An optional string describing the reason for the cancellation."""
@@ -1956,10 +1958,11 @@ class ElicitRequestURLParams(RequestParams):
url: str
"""The URL that the user should navigate to."""
elicitation_id: str
elicitation_id: str | None = None
"""The ID of the elicitation, which must be unique within the context of the server.
The client MUST treat this ID as an opaque value.
The client MUST treat this ID as an opaque value. Required on the wire at
2025-11-25; removed at 2026-07-28.
"""
task: TaskMetadata | None = None
@@ -309,7 +309,7 @@ async def test_elicitation_complete_notification_carries_the_elicited_id_back_to
returns; the same ordering already holds on in-memory and SSE transports.
"""
elicitation_id = "auth-001"
elicited_ids: list[str] = []
elicited_ids: list[str | None] = []
received: list[IncomingMessage] = []
async def collect(message: IncomingMessage) -> None:
+27
View File
@@ -674,6 +674,33 @@ def test_embedded_input_request_entries_without_method_reject_at_the_surface_ste
methods.parse_server_result("tools/call", "2026-07-28", body)
def test_input_required_url_elicit_without_elicitation_id_parses_at_2026():
"""A 2026-07-28 `InputRequiredResult` embedding a URL-mode elicitation parses
through both the surface and monolith steps without `elicitationId`.
Spec-mandated: the field is required at 2025-11-25 only and removed at
2026-07-28; the monolith model carries it as optional so the superset can
accept both versions.
"""
body = {
"resultType": "input_required",
"inputRequests": {
"r1": {
"method": "elicitation/create",
"params": {"mode": "url", "message": "Please sign in", "url": "https://example.com/auth"},
}
},
}
parsed = methods.parse_server_result("tools/call", "2026-07-28", body)
assert isinstance(parsed, types.InputRequiredResult)
assert parsed.input_requests is not None
request = parsed.input_requests["r1"]
assert isinstance(request, types.ElicitRequest)
assert isinstance(request.params, types.ElicitRequestURLParams)
assert request.params.url == "https://example.com/auth"
assert request.params.elicitation_id is None
def test_none_params_omit_the_key_so_required_params_reject():
with pytest.raises(pydantic.ValidationError) as excinfo:
methods.parse_client_request("tools/call", "2025-11-25", None)