Files
modelcontextprotocol--pytho…/tests/shared/test_message.py
Max Isbey c53aefd293 Close cancelled HTTP exchanges and harden auth validation
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.
2026-06-28 11:30:26 +00:00

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()