Relax monolith ElicitRequestURLParams.elicitation_id for 2026-07-28 (#2913)
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user