Skip to content

Commit 6f067d5

Browse files
committed
Make interaction-manifest behaviour strings match what their tests prove
Sixteen fixes on sixteen entries, each evidenced against the bound test body or the spec text: narrow over-claiming strings to the assertions that exist (403-scope-upgrade's unproven no-loop clause, iss mismatch-only coverage, tools-only registration, arrival-only HTTP notification delivery, the custom-client auth clause), correct two entries that attributed the modern classifier's version rejection to the legacy transport, record the 2026 per-request logLevel gate divergence on the Context logging helper and the list-vs-object structured-content gap on text-mirror, re-ground the null-id deferral on the now-existing fault channel, and fix the discover result field name (supportedVersions) plus the 404 status the spec mandates for initialize at the modern entry. Cells unchanged (830 before and after).
1 parent 18083fe commit 6f067d5

1 file changed

Lines changed: 52 additions & 31 deletions

File tree

‎tests/interaction/_requirements.py‎

Lines changed: 52 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -383,10 +383,15 @@ def __post_init__(self) -> None:
383383
source=f"{SPEC_2026_BASE_URL}/basic#_meta",
384384
behavior=(
385385
"Every client→server request on a modern-negotiated session carries "
386-
"_meta.{protocolVersion,clientInfo,clientCapabilities}; notifications do not."
386+
"_meta.{protocolVersion,clientInfo,clientCapabilities}."
387387
),
388388
added_in="2026-07-28",
389389
supersedes=("lifecycle:initialize:client-info", "lifecycle:initialize:client-capabilities"),
390+
note=(
391+
"The spec MUST covers requests only. The session's modern stamp is message-agnostic, so "
392+
"session-sent notifications carry the envelope too, while dispatcher-built frames (the "
393+
"courtesy cancel) do not; neither notification arm is asserted here."
394+
),
390395
),
391396
"lifecycle:envelope:header-matches-meta": Requirement(
392397
source=f"{SPEC_2026_BASE_URL}/basic/transports/streamable-http#protocol-version-header",
@@ -415,18 +420,25 @@ def __post_init__(self) -> None:
415420
"lifecycle:discover:fallback-method-not-found": Requirement(
416421
source=f"{SPEC_2026_BASE_URL}/basic/transports/stdio#backward-compatibility",
417422
behavior=(
418-
"When server/discover returns any JSON-RPC error or a bare HTTP 4xx, an auto-negotiating "
419-
"client falls back to the legacy initialize handshake and the connection succeeds at a "
420-
"handshake-era version (legacy servers reject the probe with various codes)."
423+
"When server/discover returns a JSON-RPC error that is not a recognized modern negotiation "
424+
"error (-32022 retries or raises instead; see lifecycle:discover:retry-on-32022), or a bare "
425+
"HTTP 4xx, an auto-negotiating client falls back to the legacy initialize handshake and the "
426+
"connection succeeds at a handshake-era version; the fallback is not keyed to specific codes "
427+
"(legacy servers reject the probe with various codes)."
421428
),
422429
added_in="2026-07-28",
430+
note=(
431+
"The SDK keys its no-fallback carve-out to -32022 alone, while the spec's carve-out is any "
432+
"recognized modern JSON-RPC error (an open set); no test drives a modern-error probe "
433+
"rejection other than -32022."
434+
),
423435
),
424436
"lifecycle:discover:network-error-raises": Requirement(
425437
source="sdk",
426438
behavior=(
427439
"A network/connection error during server/discover propagates to the caller without "
428-
"falling back to initialize; any rpc-error or 4xx falls back (legacy servers reject the "
429-
"probe with various codes). An outage is never an era verdict."
440+
"falling back to initialize; fallback is reserved for server rejections (see "
441+
"lifecycle:discover:fallback-method-not-found). An outage is never an era verdict."
430442
),
431443
transports=("streamable-http", "streamable-http-stateless"),
432444
added_in="2026-07-28",
@@ -655,14 +667,15 @@ def __post_init__(self) -> None:
655667
note=(
656668
"The dispatcher drops null-id error responses with a debug log; in v1, JSONRPCError.id was "
657669
"non-nullable, so a null-id error response failed transport validation and the resulting "
658-
"ValidationError was surfaced to message_handler as an exception. A typed fault channel "
659-
"restoring visibility is planned before v2 stable."
670+
"ValidationError was surfaced to message_handler as an exception. The v2 fault channel "
671+
"exists (message_handler receives stream exceptions), but response routing drops the "
672+
"null-id error before anything reaches it."
660673
),
661674
),
662675
deferred=(
663676
"Not yet covered here: the current drop is pinned at the dispatcher level by "
664-
"tests/shared/test_jsonrpc_dispatcher.py; an interaction-level test waits on the planned "
665-
"fault channel."
677+
"tests/shared/test_jsonrpc_dispatcher.py; an interaction-level test waits on the dispatcher "
678+
"routing null-id errors into the existing fault channel."
666679
),
667680
),
668681
"protocol:meta:related-task": Requirement(
@@ -893,6 +906,13 @@ def __post_init__(self) -> None:
893906
"tools:call:structured-content:text-mirror": Requirement(
894907
source=f"{SPEC_BASE_URL}/server/tools#structured-content",
895908
behavior="A tool returning structured content also returns the serialized JSON as a text content block.",
909+
divergence=Divergence(
910+
note=(
911+
"Holds for object returns (the bound test pins the serialized-JSON mirror); a "
912+
"list-returning tool yields one text block per element rather than the serialized JSON "
913+
"of its structured value (pinned by the test on mcpserver:tool:output-schema:wrapped)."
914+
),
915+
),
896916
),
897917
"tools:call:unknown-name": Requirement(
898918
source=f"{SPEC_BASE_URL}/server/tools#error-handling",
@@ -1094,6 +1114,13 @@ def __post_init__(self) -> None:
10941114
"The Context logging helpers (debug/info/warning/error) send log message notifications at the "
10951115
"corresponding severity."
10961116
),
1117+
divergence=Divergence(
1118+
note=(
1119+
"At 2026-07-28 the spec forbids notifications/message for a request whose _meta lacks the "
1120+
"io.modelcontextprotocol/logLevel opt-in; the Context helpers never read that key and emit "
1121+
"unconditionally, so a bound test pins the un-gated delivery on the live 2026-07-28 cells."
1122+
),
1123+
),
10971124
),
10981125
"mcpserver:context:progress": Requirement(
10991126
source="sdk",
@@ -1435,7 +1462,7 @@ def __post_init__(self) -> None:
14351462
behavior="prompts/get for a name that was never registered returns JSON-RPC error -32602 (Invalid params).",
14361463
divergence=Divergence(
14371464
note=(
1438-
"The spec's example uses -32602 Invalid params for unknown prompts; MCPServer raises "
1465+
"The spec SHOULD-lists -32602 Invalid params for an invalid prompt name; MCPServer raises "
14391466
"ValueError, which the low-level server converts to error code 0."
14401467
),
14411468
),
@@ -2122,9 +2149,8 @@ def __post_init__(self) -> None:
21222149
"mcpserver:register:post-connect": Requirement(
21232150
source="sdk",
21242151
behavior=(
2125-
"A tool, resource, or prompt registered or removed after the client connected appears in (or "
2126-
"disappears from) the corresponding list results, and the change is announced with a "
2127-
"list_changed notification."
2152+
"A tool registered or removed after the client connected appears in (or disappears from) "
2153+
"tools/list results, and the change is announced with a list_changed notification."
21282154
),
21292155
divergence=Divergence(
21302156
note=(
@@ -2576,8 +2602,7 @@ def __post_init__(self) -> None:
25762602
"transport:streamable-http:notifications": Requirement(
25772603
source=f"{SPEC_BASE_URL}/basic/transports#streamable-http",
25782604
behavior=(
2579-
"Notifications emitted during a request are delivered on that request's SSE stream and reach "
2580-
"the client's callbacks, in order, before the response."
2605+
"Notifications emitted during a request reach the client's callbacks over the streamable HTTP framing."
25812606
),
25822607
transports=("streamable-http",),
25832608
note="Only observable over streamable HTTP: per-request SSE streams are HTTP-specific.",
@@ -3190,8 +3215,9 @@ def __post_init__(self) -> None:
31903215
"hosting:http:protocol-version-rejection-literal": Requirement(
31913216
source="sdk",
31923217
behavior=(
3193-
"The legacy streamable-HTTP transport's version-rejection body contains the literal substring "
3194-
"'Unsupported protocol version', which other-SDK clients substring-match during negotiation."
3218+
"The streamable-HTTP version-rejection body contains the literal substring 'Unsupported "
3219+
"protocol version', which other-SDK clients substring-match during negotiation; the modern "
3220+
"request classifier is its only emission site."
31953221
),
31963222
transports=("streamable-http",),
31973223
note=(
@@ -3233,17 +3259,18 @@ def __post_init__(self) -> None:
32333259
),
32343260
"hosting:http:modern:initialize-removed": Requirement(
32353261
source=f"{SPEC_2026_BASE_URL}/basic/index",
3236-
behavior="A 2026-07-28 initialize request is answered with METHOD_NOT_FOUND.",
3262+
behavior="A 2026-07-28 initialize request is answered with METHOD_NOT_FOUND at HTTP 404.",
32373263
added_in="2026-07-28",
32383264
transports=("streamable-http",),
32393265
note=("Only observable over streamable HTTP: the modern entry's method registry omits initialize."),
32403266
),
32413267
"hosting:http:modern:legacy-fallthrough": Requirement(
32423268
source=f"{SPEC_2026_BASE_URL}/basic/versioning",
32433269
behavior=(
3244-
"Non-2026-07-28 traffic on the same /mcp endpoint reaches the legacy transport "
3245-
"byte-unchanged: a 2025-era initialize handshake still completes, and an unrecognised "
3246-
"MCP-Protocol-Version header still produces the legacy 400 'Unsupported protocol version' literal."
3270+
"Initialize-handshake-era traffic on the same /mcp endpoint reaches the legacy transport "
3271+
"byte-unchanged: a 2025-era initialize handshake still completes. Any other "
3272+
"MCP-Protocol-Version header routes to the modern entry, whose validation ladder rejects "
3273+
"the envelope-less request with 400 INVALID_PARAMS."
32473274
),
32483275
added_in="2026-07-28",
32493276
transports=("streamable-http",),
@@ -3388,10 +3415,7 @@ def __post_init__(self) -> None:
33883415
),
33893416
"client-transport:http:custom-client": Requirement(
33903417
source="sdk",
3391-
behavior=(
3392-
"A caller-supplied HTTP client (and its event hooks and headers) is used for all MCP traffic, "
3393-
"including auth flows."
3394-
),
3418+
behavior="A caller-supplied HTTP client (and its event hooks and headers) is used for all MCP traffic.",
33953419
transports=("streamable-http",),
33963420
note="Only observable over HTTP: the httpx2 client is HTTP-specific.",
33973421
),
@@ -3563,9 +3587,7 @@ def __post_init__(self) -> None:
35633587
),
35643588
"client-auth:403-scope-upgrade": Requirement(
35653589
source=f"{SPEC_BASE_URL}/basic/authorization#step-up-authorization-flow",
3566-
behavior=(
3567-
"A 403 with WWW-Authenticate triggers a scope-upgrade authorization attempt; repeated 403s do not loop."
3568-
),
3590+
behavior="A 403 with WWW-Authenticate triggers a scope-upgrade authorization attempt.",
35693591
transports=("streamable-http",),
35703592
note="OAuth is HTTP-only.",
35713593
),
@@ -3812,8 +3834,7 @@ def __post_init__(self) -> None:
38123834
source=f"{SPEC_2026_BASE_URL}/basic/authorization#authorization-response-validation",
38133835
behavior=(
38143836
"The client validates the RFC 9207 iss authorization-response parameter against the "
3815-
"authorization server issuer (simple string comparison) and rejects a mismatch, or a "
3816-
"missing iss when the server advertises support (SEP-2468)."
3837+
"authorization server issuer (simple string comparison) and rejects a mismatch (SEP-2468)."
38173838
),
38183839
added_in="2026-07-28",
38193840
transports=("streamable-http",),

0 commit comments

Comments
 (0)