Skip to content

Commit daf7108

Browse files
committed
tests/interaction: tag arm_exclusions per-requirement, not minimal-covering-set
The earlier annotation passes minimised by tagging only one requirement ID per multi-decorated test, relying on compute_cells() intersection to drop the cell. That left the manifest's per-requirement semantics incomplete: the ArmExclusionReason enum is documented as a re-admission checklist (grep the reason to find what to re-admit), which only works if every semantically-excluded requirement carries the exclusion. 15 entries now carry their own arm_exclusions instead of inheriting via a sibling on the same test: - 12 server-initiated-request / requires-session entries gain both the streamable-http-stateless and spec_version=2026-07-28 exclusions (sampling:create:model-preferences/system-prompt, elicitation:form: basic/action:accept/schema:enum-variants, etc.) - 3 capability:declared entries (resources/prompts/completion) gain the legacy-only-vocabulary 2026 exclusion, matching tools:capability:declared lifecycle:initialize:capabilities:from-handlers and mcpserver:context: logging are deliberately not tagged (open classification question / era-agnostic respectively). Also: the README transport-matrix section now mentions all four connectable transports and the stateless carve-out, matching the era-axis section. 504 connect cells unchanged; 53 stateless / 56 2026 arm_exclusions; 100% coverage.
1 parent bb822e0 commit daf7108

2 files changed

Lines changed: 57 additions & 4 deletions

File tree

tests/interaction/README.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,12 @@ test body — each directory pins its flavour's true output exactly.
5656

5757
Transport-agnostic tests take the `connect` fixture instead of constructing `Client(server)`
5858
directly, and therefore run once per transport: over the in-memory transport, over the server's
59-
real streamable HTTP app driven in-process through the streaming bridge, and over the legacy SSE
60-
transport the same way. A test connects with `async with connect(server, ...) as client:` and
61-
asserts the same output on every leg, because the transport is not supposed to change observable
62-
behaviour. Tests that are tied to one transport do not use the fixture: the wire-recording tests
59+
real streamable HTTP app driven in-process through the streaming bridge (in both stateful and
60+
stateless configurations), and over the legacy SSE transport the same way. A test connects with
61+
`async with connect(server, ...) as client:` and asserts the same output on every leg, because the
62+
transport is not supposed to change observable behaviour. Requirements that need a server-to-client
63+
back-channel or persisted session state are carved out of the stateless arm via `arm_exclusions`.
64+
Tests that are tied to one transport do not use the fixture: the wire-recording tests
6365
(their seam is the in-memory stream pair), the bare-`ClientSession` lifecycle tests, the
6466
real-clock timeout tests (the timeout machinery is transport-independent and must not race
6567
transport latency), and everything under `transports/`, which pins behaviour only observable on

tests/interaction/_requirements.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,10 @@ def __post_init__(self) -> None:
351351
"protocol:cancel:handler-abort-propagates": Requirement(
352352
source=f"{SPEC_BASE_URL}/basic/utilities/cancellation#behavior-requirements",
353353
behavior="On the receiving side, a cancellation notification stops the running request handler.",
354+
arm_exclusions=(
355+
ArmExclusion(reason="requires-session", transport="streamable-http-stateless"),
356+
ArmExclusion(reason="requires-session", spec_version="2026-07-28"),
357+
),
354358
),
355359
"protocol:cancel:in-flight": Requirement(
356360
source=f"{SPEC_BASE_URL}/basic/utilities/cancellation#behavior-requirements",
@@ -535,6 +539,10 @@ def __post_init__(self) -> None:
535539
"A progress notification that arrives after its request has completed is not delivered to the "
536540
"original progress callback."
537541
),
542+
arm_exclusions=(
543+
ArmExclusion(reason="requires-session", transport="streamable-http-stateless"),
544+
ArmExclusion(reason="requires-session", spec_version="2026-07-28"),
545+
),
538546
),
539547
"protocol:progress:no-token": Requirement(
540548
source=f"{SPEC_BASE_URL}/basic/utilities/progress#progress-flow",
@@ -660,6 +668,10 @@ def __post_init__(self) -> None:
660668
"A tool handler that issues a sampling request receives the client's completion and can embed "
661669
"it in the tool call result."
662670
),
671+
arm_exclusions=(
672+
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
673+
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
674+
),
663675
),
664676
"tools:call:structured-content": Requirement(
665677
source=f"{SPEC_BASE_URL}/server/tools#structured-content",
@@ -882,6 +894,10 @@ def __post_init__(self) -> None:
882894
"Context.elicit sends a form elicitation built from a typed schema and returns a typed "
883895
"accepted/declined/cancelled result."
884896
),
897+
arm_exclusions=(
898+
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
899+
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
900+
),
885901
),
886902
"mcpserver:context:read-resource": Requirement(
887903
source="sdk",
@@ -906,6 +922,7 @@ def __post_init__(self) -> None:
906922
"A server with resource handlers advertises the resources capability, including the subscribe "
907923
"sub-flag when a subscribe handler is registered."
908924
),
925+
arm_exclusions=(ArmExclusion(reason="legacy-only-vocabulary", spec_version="2026-07-28"),),
909926
),
910927
"resources:list-changed": Requirement(
911928
source=f"{SPEC_BASE_URL}/server/resources#list-changed-notification",
@@ -1062,6 +1079,7 @@ def __post_init__(self) -> None:
10621079
"prompts:capability:declared": Requirement(
10631080
source=f"{SPEC_BASE_URL}/server/prompts#capabilities",
10641081
behavior="A server with a list_prompts handler advertises the prompts capability in its initialize result.",
1082+
arm_exclusions=(ArmExclusion(reason="legacy-only-vocabulary", spec_version="2026-07-28"),),
10651083
),
10661084
"prompts:get:content:audio": Requirement(
10671085
source=f"{SPEC_BASE_URL}/server/prompts#audio-content",
@@ -1164,6 +1182,7 @@ def __post_init__(self) -> None:
11641182
"completion:capability:declared": Requirement(
11651183
source=f"{SPEC_BASE_URL}/server/utilities/completion#capabilities",
11661184
behavior="A server with a completion handler advertises the completions capability in its initialize result.",
1185+
arm_exclusions=(ArmExclusion(reason="legacy-only-vocabulary", spec_version="2026-07-28"),),
11671186
),
11681187
"completion:complete:not-supported": Requirement(
11691188
source=f"{SPEC_BASE_URL}/server/utilities/completion#capabilities",
@@ -1308,17 +1327,29 @@ def __post_init__(self) -> None:
13081327
"capability; the server-side validator only checks tools/tool_choice."
13091328
),
13101329
),
1330+
arm_exclusions=(
1331+
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
1332+
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
1333+
),
13111334
),
13121335
"sampling:create:model-preferences": Requirement(
13131336
source=f"{SPEC_BASE_URL}/client/sampling#model-preferences",
13141337
behavior=(
13151338
"The model preferences supplied by the server (hints and the cost, speed, and intelligence "
13161339
"priorities) reach the client callback intact."
13171340
),
1341+
arm_exclusions=(
1342+
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
1343+
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
1344+
),
13181345
),
13191346
"sampling:create:system-prompt": Requirement(
13201347
source=f"{SPEC_BASE_URL}/client/sampling#creating-messages",
13211348
behavior="The system prompt supplied by the server reaches the client callback intact.",
1349+
arm_exclusions=(
1350+
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
1351+
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
1352+
),
13221353
),
13231354
"sampling:create:tools": Requirement(
13241355
source=f"{SPEC_BASE_URL}/client/sampling#tools-in-sampling",
@@ -1494,13 +1525,21 @@ def __post_init__(self) -> None:
14941525
"elicitation/create; the spec's MUST NOT is not enforced."
14951526
),
14961527
),
1528+
arm_exclusions=(
1529+
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
1530+
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
1531+
),
14971532
),
14981533
"elicitation:form:action:accept": Requirement(
14991534
source=f"{SPEC_BASE_URL}/client/elicitation#response-actions",
15001535
behavior=(
15011536
"A form-mode elicitation answered with action 'accept' returns the user's content to the "
15021537
"requesting handler."
15031538
),
1539+
arm_exclusions=(
1540+
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
1541+
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
1542+
),
15041543
),
15051544
"elicitation:form:action:cancel": Requirement(
15061545
source=f"{SPEC_BASE_URL}/client/elicitation#response-actions",
@@ -1524,6 +1563,10 @@ def __post_init__(self) -> None:
15241563
"A form-mode elicitation delivers the message and requested schema to the client callback "
15251564
"exactly as the server sent them."
15261565
),
1566+
arm_exclusions=(
1567+
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
1568+
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
1569+
),
15271570
),
15281571
"elicitation:form:defaults": Requirement(
15291572
source=f"{SPEC_BASE_URL}/client/elicitation#requested-schema",
@@ -1560,6 +1603,10 @@ def __post_init__(self) -> None:
15601603
"Requested-schema enum fields (including titled and multi-select variants) reach the client "
15611604
"callback as sent."
15621605
),
1606+
arm_exclusions=(
1607+
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
1608+
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
1609+
),
15631610
),
15641611
"elicitation:form:schema:primitives": Requirement(
15651612
source=f"{SPEC_BASE_URL}/client/elicitation#requested-schema",
@@ -1615,6 +1662,10 @@ def __post_init__(self) -> None:
16151662
"response carries no content (accept means the user agreed to visit the URL, not that the "
16161663
"interaction completed)."
16171664
),
1665+
arm_exclusions=(
1666+
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
1667+
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
1668+
),
16181669
),
16191670
"elicitation:url:basic": Requirement(
16201671
source=f"{SPEC_BASE_URL}/client/elicitation#url-mode-elicitation-requests",

0 commit comments

Comments
 (0)