Skip to content

Commit f38302d

Browse files
committed
Re-ground deferral premises and stale notes against the current main
The merge window landed the listen server runtime, the client response cache, requestState integrity, stream-pair 2026 serving, and the client extension API — invalidating the premises of ~45 deferral texts and notes written against the older base. Each text now states what is true at this pin: entries whose blocking surface landed flip to 'Not yet covered here' naming what a test will drive; entries still blocked name the precise missing surface. Also fixes the README era-lock line (stateless is locked too), documents the default-on client cache in the transport-matrix section, and renames one resource test whose name predated its retarget.
1 parent 326a600 commit f38302d

8 files changed

Lines changed: 291 additions & 192 deletions

File tree

tests/interaction/README.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ stateless configurations), and over the legacy SSE transport the same way. A tes
6161
`async with connect(server, ...) as client:` and asserts the same output on every leg, because the
6262
transport is not supposed to change observable behaviour. Requirements that need a server-to-client
6363
back-channel or persisted session state are carved out of the stateless arm via `arm_exclusions`.
64+
65+
The 2026 cells run the client's response cache in its default-on configuration. Servers stamp
66+
`ttlMs: 0` by default, so nothing is served from cache unless a test opts in server-side by
67+
authoring a positive `ttl_ms` — a test that does so and then repeats a call must expect the
68+
repeat to be served from cache instead of reaching the handler.
69+
6470
Tests that are tied to one transport do not use the fixture: the wire-recording tests
6571
(their seam is the in-memory stream pair), the bare-`ClientSession` lifecycle tests, the
6672
real-clock timeout tests (the timeout machinery is transport-independent and must not race
@@ -163,10 +169,11 @@ What admits or excludes a cell:
163169
closes, grep for the reason string to find every cell to re-admit.
164170
- **`known_failures`** keep a cell in the grid but mark it as a strict xfail — the test runs and
165171
must fail; an unexpected pass fails the suite.
166-
- **`TRANSPORT_SPEC_VERSIONS`** era-locks a transport to a subset of spec versions (currently only
167-
`sse` is locked to `2025-11-25`). A `(transport, version)` cell is dropped if the version is not
168-
in the transport's entry; transports absent from the map serve every spec version. This is the
169-
mechanism for cutting an entire transport off from a new revision (or admitting it).
172+
- **`TRANSPORT_SPEC_VERSIONS`** era-locks a transport to a subset of spec versions (currently
173+
`sse` and `streamable-http-stateless` are locked to `2025-11-25`). A `(transport, version)`
174+
cell is dropped if the version is not in the transport's entry; transports absent from the
175+
map serve every spec version. This is the mechanism for cutting an entire transport off from
176+
a new revision (or admitting it).
170177
- **`transports`** is descriptive metadata for the non-`connect` transport-specific suites under
171178
`transports/` and does **not** drive cell generation. Only `arm_exclusions`, `added_in`,
172179
`removed_in`, and `TRANSPORT_SPEC_VERSIONS` filter the grid.

tests/interaction/_connect.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,9 @@ def httpx_client_factory(
388388
transport = sse_client(f"{BASE_URL}/sse", httpx_client_factory=httpx_client_factory)
389389
async with Client(
390390
transport,
391-
# SSE is a legacy-only transport; the modern path has no SSE story.
391+
# A policy lock, not a capability one: the dual-era server loop behind build_sse_app
392+
# would negotiate 2026 if probed, but SSE is the deprecated legacy transport and its
393+
# clients run the handshake era by design.
392394
mode="legacy",
393395
read_timeout_seconds=read_timeout_seconds,
394396
sampling_callback=sampling_callback,

tests/interaction/_requirements.py

Lines changed: 255 additions & 174 deletions
Large diffs are not rendered by default.

tests/interaction/lowlevel/test_caching.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"""SEP-2549 caching hints: producer-side stamping and client-facing TTL/scope semantics.
22
33
One test pins 2026 wire frames (typed models hide absent-vs-default keys); one scripts a
4-
non-conformant server (the typed Server cannot author the malformed value); response caching is deferred.
4+
non-conformant server (the typed Server cannot author the malformed value). The client response
5+
cache is live but its serve/evict behaviours are not yet pinned here (see the caching:* deferrals).
56
"""
67

78
import anyio
@@ -160,8 +161,9 @@ async def list_tools(ctx: ServerRequestContext, params: types.PaginatedRequestPa
160161
async def test_ttl_zero_results_are_refetched_on_every_access(connect: Connect) -> None:
161162
"""Two consecutive list_tools calls against a ttlMs-0 server both reach the handler.
162163
163-
Passes by construction (the client has no response cache); the pin is the regression bar for
164-
a future cache that wrongly serves a ttlMs-0 entry.
164+
Load-bearing against the live response cache: a ttlMs-0 result is never stored, so every
165+
access re-fetches, while the same seam with a positive ttl_ms serves the second access from
166+
cache (one fetch).
165167
"""
166168
fetches: list[int] = []
167169

tests/interaction/lowlevel/test_client_connect.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,10 @@ async def test_discover_carries_server_instructions_and_omits_them_when_undeclar
394394
async def test_discover_capabilities_reflect_registered_handlers() -> None:
395395
"""The discover result advertises a capability per registered handler area and omits the rest.
396396
397-
Only era-clean areas are registered: the derivation is era-agnostic, so a subscribe or logging
398-
handler would advertise an era-removed method -- a quirk deliberately left unpinned here.
397+
Only era-clean areas are registered. The subscription-delivered bits are era-honest (at 2026
398+
they derive from whether subscriptions/listen is served, so a legacy subscribe handler
399+
advertises nothing), but logging derivation is still era-agnostic: a setLevel handler would
400+
advertise the era-deprecated logging capability -- a quirk deliberately left unpinned here.
399401
"""
400402

401403
# The handlers exist only so their capability is advertised; none is ever called.

tests/interaction/lowlevel/test_resources.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,11 +177,12 @@ async def read_resource(ctx: ServerRequestContext, params: types.ReadResourceReq
177177

178178

179179
@requirement("protocol:error:handler-error-passthrough")
180-
async def test_read_resource_unknown_uri_is_protocol_error(connect: Connect) -> None:
181-
"""A handler that rejects an unrecognised URI with MCPError produces a JSON-RPC error.
180+
async def test_handler_raised_mcperror_code_and_message_reach_the_client_verbatim(connect: Connect) -> None:
181+
"""A handler-raised MCPError's code and message reach the client verbatim.
182182
183-
The spec reserves -32002 for resource-not-found; the code is the handler's choice and reaches
184-
the client verbatim.
183+
The -32002 here is only this handler's choice (the pre-2026 resource-not-found code; the 2026
184+
spec reserves -32602 for an unknown URI). The real unknown-URI posture lives in the resource
185+
registry and is pinned in mcpserver/test_resources.py; this test asserts the generic passthrough.
185186
"""
186187

187188
async def read_resource(ctx: ServerRequestContext, params: types.ReadResourceRequestParams) -> ReadResourceResult:

tests/interaction/lowlevel/test_tools.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara
546546
server = Server("weather", on_list_tools=list_tools, on_call_tool=call_tool)
547547

548548
async with connect(server) as client:
549+
# The {} args matter: on http-2026 a non-empty call adds the server's internal Mcp-Param validation listing.
549550
first = await client.call_tool("forecast", {})
550551
assert list_calls == ["called"]
551552
second = await client.call_tool("forecast", {})

tests/interaction/transports/test_hosting_http_modern.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,9 @@ async def test_modern_client_stops_mirroring_after_a_re_list_drops_the_tool() ->
548548
549549
The tool is first listed with a valid annotation (so a call mirrors `Mcp-Param-Region`), then re-listed
550550
with an invalid annotation -- the modern client drops it and evicts the cached map, so a later `tools/call`
551-
by name carries no `Mcp-Param-*` header. Asserted at the wire, where the eviction is observable.
551+
by name carries no `Mcp-Param-*` header. The server serves that header-less call only because the same
552+
invalid schema disables its own validation (the shared validator skips schemas it rejects); a valid
553+
annotated schema would reject the missing header. Asserted at the wire, where the eviction is observable.
552554
"""
553555
schema = {"type": "object", "properties": {"a": {"type": "string", "x-mcp-header": "Region"}}}
554556
bad_schema = {"type": "object", "properties": {"a": {"type": "string", "x-mcp-header": "bad name"}}}
@@ -737,9 +739,10 @@ async def on_request(request: httpx.Request) -> None:
737739
async def test_null_and_absent_annotated_arguments_emit_no_param_headers_and_the_server_accepts() -> None:
738740
"""Null and absent annotated arguments emit no ``Mcp-Param-*`` headers and the server accepts the call.
739741
740-
Spec-mandated by the behaviour matrix's null and absent rows. Acceptance currently holds
741-
because the server validates no param headers at all; when that validation lands, null and
742-
absent must not start being rejected.
742+
Spec-mandated by the behaviour matrix's null and absent rows. The fixture advertises the
743+
annotated schema, so this acceptance is a validated accept: the server checks each annotated
744+
argument against its `Mcp-Param-*` header and would reject an orphan header for the null or
745+
absent argument (a header matching no annotation is ignored).
743746
"""
744747
requests: list[httpx.Request] = []
745748

0 commit comments

Comments
 (0)