Skip to content

Commit 6cb8de1

Browse files
committed
Tighten the cancellation-suite manifest provenance and wire pins
1 parent 79c3ac3 commit 6cb8de1

4 files changed

Lines changed: 19 additions & 8 deletions

File tree

tests/interaction/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ flows — with a single subprocess test for stdio.
2727
the constants in `mcp_types`; error *message strings* are pinned only where they are the
2828
SDK's own deliberate output.
2929
- **No sleeps, no real I/O.** Concurrency is coordinated with `anyio.Event`; every wait that
30-
could hang is bounded by `anyio.fail_after(5)`. The HTTP and OAuth tests drive the Starlette
30+
could hang is bounded by `anyio.fail_after(5)`. A test that must let in-flight deliveries
31+
settle before teardown (an abandoned request's late error response, say) may use
32+
`anyio.wait_all_tasks_blocked()`: the whole suite is single-loop and task-driven, so
33+
quiescence is deterministic. The HTTP and OAuth tests drive the Starlette
3134
app in-process through the suite's streaming ASGI bridge (`transports/_bridge.py`), which
3235
delivers each response chunk as the server produces it — full duplex, but still no sockets,
3336
threads, or subprocesses anywhere outside the one stdio test.

tests/interaction/_requirements.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -475,11 +475,16 @@ def __post_init__(self) -> None:
475475
),
476476
),
477477
"protocol:request-id:caller-supplied": Requirement(
478-
source=f"{SPEC_2026_BASE_URL}/basic/patterns/subscriptions#receiving-notifications",
478+
source="sdk",
479479
behavior=(
480480
"A caller can supply the id of a request it sends, so the id is known before any response "
481481
"arrives; subscriptions/listen streams are demultiplexed by exactly that id."
482482
),
483+
note=(
484+
f"The demux-by-listen-request-id obligation is the spec's "
485+
f"({SPEC_2026_BASE_URL}/basic/patterns/subscriptions#receiving-notifications); supplying the "
486+
"id up front is the SDK surface that makes it satisfiable."
487+
),
483488
added_in="2026-07-28",
484489
deferred=(
485490
"No public API surface yet: the capability exists at the dispatcher seam "
@@ -500,8 +505,11 @@ def __post_init__(self) -> None:
500505
behavior=(
501506
"Abandoning an in-flight request client-side (cancelling the task awaiting it) cancels the "
502507
"request itself: the server-side handler stops and the session serves later requests "
503-
"normally. Each transport carries the signal in its own spelling - a cancelled frame on "
504-
"stream wires, closing the request's own response stream at 2026-07-28 streamable HTTP."
508+
"normally."
509+
),
510+
note=(
511+
"The per-transport wire spelling (frame vs response-stream close) is pinned separately by "
512+
"protocol:cancel:stream-frame and the client-transport:http:cancel-* pair."
505513
),
506514
arm_exclusions=(
507515
ArmExclusion(
@@ -580,7 +588,7 @@ def __post_init__(self) -> None:
580588
),
581589
),
582590
"protocol:cancel:stream-frame": Requirement(
583-
source=f"{SPEC_2026_BASE_URL}/basic/utilities/cancellation#transport-specific-cancellation",
591+
source=f"{SPEC_2026_BASE_URL}/basic/patterns/cancellation#transport-specific-cancellation",
584592
behavior=(
585593
"On stream (stdio-shaped) wires at 2026-07-28, abandoning an in-flight request sends exactly "
586594
"one notifications/cancelled naming its request id - streams keep the frame spelling of "

tests/interaction/lowlevel/test_cancellation.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -397,7 +397,7 @@ async def call_and_abandon() -> None:
397397
# dropped while the client is still open, so teardown never races its delivery.
398398
await anyio.wait_all_tasks_blocked()
399399
result = await client.call_tool("echo", {})
400-
assert result.content == [TextContent(text="ok")]
400+
assert result == snapshot(CallToolResult(content=[TextContent(text="ok")]))
401401

402402

403403
@requirement("protocol:cancel:abort-scoped")

tests/interaction/transports/test_client_transport_http.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -307,8 +307,8 @@ async def call_and_abandon() -> None:
307307
result = await client.call_tool("echo", {})
308308
assert result.content == [TextContent(text="ok")]
309309

310-
methods = [json.loads(body)["method"] for method, body in requests if method == "POST"]
311-
assert methods == snapshot(["tools/list", "tools/call", "tools/call"])
310+
wire = [(method, json.loads(body)["method"] if body else None) for method, body in requests]
311+
assert wire == snapshot([("POST", "tools/list"), ("POST", "tools/call"), ("POST", "tools/call")])
312312

313313

314314
@requirement("client-transport:http:cancel-posts-frame")

0 commit comments

Comments
 (0)