c53aefd293
Review-feedback round on the conformance burn-down: - Cancelled requests no longer leave the legacy streamable-HTTP POST hanging. The dispatcher emits a RequestSettled marker when a handler is cancelled without producing a response; the transport consumes it by closing the per-request stream, so the POST's SSE stream terminates without a response frame and JSON-response mode completes with 204 No Content (the client treats 202/204 alike). Per-request streams are released instead of leaking until session teardown, and a handler that survives the cancellation still delivers its normal response. The marker is type-visible on the dispatcher write stream and is stripped by every serializing transport, so it can never appear on a wire. - A bearer token whose audience cannot be canonicalized (out-of-range or non-numeric port) is now rejected with the standard 401 invalid_token instead of raising through the auth middleware as a 500. - The bundled authorization server's /register now accepts only https redirect URIs or http on a loopback host; other schemes on loopback hosts (ftp, ws, javascript, custom) are rejected. - OAuth client scope selection falls back to the caller-configured OAuthClientMetadata.scope when neither the WWW-Authenticate challenge nor protected-resource metadata names scopes, matching the TypeScript SDK, so the documented migration path works as written. - The cross-dispatcher contract that handler-raised MCPError subclasses surface to callers as plain MCPError is now pinned by an explicit test and documented; rehydrate with from_error when the subclass matters. - Docs: migration notes for the bearer-challenge wire-shape changes and the cancellation wire spellings; story READMEs updated to the landed error contract; strict-capabilities doc corrected to state that resources/unsubscribe is gated by the base resources capability only.
25 lines
1.0 KiB
Python
25 lines
1.0 KiB
Python
"""Tests for the transport-facing helpers in `mcp.shared.message`."""
|
|
|
|
import anyio
|
|
import pytest
|
|
from mcp_types import JSONRPCNotification
|
|
|
|
from mcp.shared.message import RequestSettled, SessionMessage, wire_messages
|
|
|
|
|
|
@pytest.mark.anyio
|
|
async def test_wire_messages_strips_settled_markers_and_preserves_frame_order():
|
|
"""`wire_messages` yields only serializable frames: `RequestSettled` markers are dropped (they
|
|
must never reach any wire) and the surviving frames keep their order."""
|
|
send, receive = anyio.create_memory_object_stream[SessionMessage | RequestSettled](3)
|
|
first = SessionMessage(JSONRPCNotification(jsonrpc="2.0", method="notifications/first"))
|
|
last = SessionMessage(JSONRPCNotification(jsonrpc="2.0", method="notifications/last"))
|
|
send.send_nowait(first)
|
|
send.send_nowait(RequestSettled(request_id=1))
|
|
send.send_nowait(last)
|
|
send.close()
|
|
|
|
with anyio.fail_after(5):
|
|
assert [frame async for frame in wire_messages(receive)] == [first, last]
|
|
receive.close()
|