Skip to content

Commit 4a214bd

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 10f8178 commit 4a214bd

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",
@@ -657,14 +669,15 @@ def __post_init__(self) -> None:
657669
note=(
658670
"The dispatcher drops null-id error responses with a debug log; in v1, JSONRPCError.id was "
659671
"non-nullable, so a null-id error response failed transport validation and the resulting "
660-
"ValidationError was surfaced to message_handler as an exception. A typed fault channel "
661-
"restoring visibility is planned before v2 stable."
672+
"ValidationError was surfaced to message_handler as an exception. The v2 fault channel "
673+
"exists (message_handler receives stream exceptions), but response routing drops the "
674+
"null-id error before anything reaches it."
662675
),
663676
),
664677
deferred=(
665678
"Not yet covered here: the current drop is pinned at the dispatcher level by "
666-
"tests/shared/test_jsonrpc_dispatcher.py; an interaction-level test waits on the planned "
667-
"fault channel."
679+
"tests/shared/test_jsonrpc_dispatcher.py; an interaction-level test waits on the dispatcher "
680+
"routing null-id errors into the existing fault channel."
668681
),
669682
),
670683
"protocol:meta:related-task": Requirement(
@@ -895,6 +908,13 @@ def __post_init__(self) -> None:
895908
"tools:call:structured-content:text-mirror": Requirement(
896909
source=f"{SPEC_BASE_URL}/server/tools#structured-content",
897910
behavior="A tool returning structured content also returns the serialized JSON as a text content block.",
911+
divergence=Divergence(
912+
note=(
913+
"Holds for object returns (the bound test pins the serialized-JSON mirror); a "
914+
"list-returning tool yields one text block per element rather than the serialized JSON "
915+
"of its structured value (pinned by the test on mcpserver:tool:output-schema:wrapped)."
916+
),
917+
),
898918
),
899919
"tools:call:unknown-name": Requirement(
900920
source=f"{SPEC_BASE_URL}/server/tools#error-handling",
@@ -1096,6 +1116,13 @@ def __post_init__(self) -> None:
10961116
"The Context logging helpers (debug/info/warning/error) send log message notifications at the "
10971117
"corresponding severity."
10981118
),
1119+
divergence=Divergence(
1120+
note=(
1121+
"At 2026-07-28 the spec forbids notifications/message for a request whose _meta lacks the "
1122+
"io.modelcontextprotocol/logLevel opt-in; the Context helpers never read that key and emit "
1123+
"unconditionally, so a bound test pins the un-gated delivery on the live 2026-07-28 cells."
1124+
),
1125+
),
10991126
),
11001127
"mcpserver:context:progress": Requirement(
11011128
source="sdk",
@@ -1437,7 +1464,7 @@ def __post_init__(self) -> None:
14371464
behavior="prompts/get for a name that was never registered returns JSON-RPC error -32602 (Invalid params).",
14381465
divergence=Divergence(
14391466
note=(
1440-
"The spec's example uses -32602 Invalid params for unknown prompts; MCPServer raises "
1467+
"The spec SHOULD-lists -32602 Invalid params for an invalid prompt name; MCPServer raises "
14411468
"ValueError, which the low-level server converts to error code 0."
14421469
),
14431470
),
@@ -2124,9 +2151,8 @@ def __post_init__(self) -> None:
21242151
"mcpserver:register:post-connect": Requirement(
21252152
source="sdk",
21262153
behavior=(
2127-
"A tool, resource, or prompt registered or removed after the client connected appears in (or "
2128-
"disappears from) the corresponding list results, and the change is announced with a "
2129-
"list_changed notification."
2154+
"A tool registered or removed after the client connected appears in (or disappears from) "
2155+
"tools/list results, and the change is announced with a list_changed notification."
21302156
),
21312157
divergence=Divergence(
21322158
note=(
@@ -2594,8 +2620,7 @@ def __post_init__(self) -> None:
25942620
"transport:streamable-http:notifications": Requirement(
25952621
source=f"{SPEC_BASE_URL}/basic/transports#streamable-http",
25962622
behavior=(
2597-
"Notifications emitted during a request are delivered on that request's SSE stream and reach "
2598-
"the client's callbacks, in order, before the response."
2623+
"Notifications emitted during a request reach the client's callbacks over the streamable HTTP framing."
25992624
),
26002625
transports=("streamable-http",),
26012626
note="Only observable over streamable HTTP: per-request SSE streams are HTTP-specific.",
@@ -3219,8 +3244,9 @@ def __post_init__(self) -> None:
32193244
"hosting:http:protocol-version-rejection-literal": Requirement(
32203245
source="sdk",
32213246
behavior=(
3222-
"The legacy streamable-HTTP transport's version-rejection body contains the literal substring "
3223-
"'Unsupported protocol version', which other-SDK clients substring-match during negotiation."
3247+
"The streamable-HTTP version-rejection body contains the literal substring 'Unsupported "
3248+
"protocol version', which other-SDK clients substring-match during negotiation; the modern "
3249+
"request classifier is its only emission site."
32243250
),
32253251
transports=("streamable-http",),
32263252
note=(
@@ -3262,17 +3288,18 @@ def __post_init__(self) -> None:
32623288
),
32633289
"hosting:http:modern:initialize-removed": Requirement(
32643290
source=f"{SPEC_2026_BASE_URL}/basic/index",
3265-
behavior="A 2026-07-28 initialize request is answered with METHOD_NOT_FOUND.",
3291+
behavior="A 2026-07-28 initialize request is answered with METHOD_NOT_FOUND at HTTP 404.",
32663292
added_in="2026-07-28",
32673293
transports=("streamable-http",),
32683294
note=("Only observable over streamable HTTP: the modern entry's method registry omits initialize."),
32693295
),
32703296
"hosting:http:modern:legacy-fallthrough": Requirement(
32713297
source=f"{SPEC_2026_BASE_URL}/basic/versioning",
32723298
behavior=(
3273-
"Non-2026-07-28 traffic on the same /mcp endpoint reaches the legacy transport "
3274-
"byte-unchanged: a 2025-era initialize handshake still completes, and an unrecognised "
3275-
"MCP-Protocol-Version header still produces the legacy 400 'Unsupported protocol version' literal."
3299+
"Initialize-handshake-era traffic on the same /mcp endpoint reaches the legacy transport "
3300+
"byte-unchanged: a 2025-era initialize handshake still completes. Any other "
3301+
"MCP-Protocol-Version header routes to the modern entry, whose validation ladder rejects "
3302+
"the envelope-less request with 400 INVALID_PARAMS."
32763303
),
32773304
added_in="2026-07-28",
32783305
transports=("streamable-http",),
@@ -3417,10 +3444,7 @@ def __post_init__(self) -> None:
34173444
),
34183445
"client-transport:http:custom-client": Requirement(
34193446
source="sdk",
3420-
behavior=(
3421-
"A caller-supplied HTTP client (and its event hooks and headers) is used for all MCP traffic, "
3422-
"including auth flows."
3423-
),
3447+
behavior="A caller-supplied HTTP client (and its event hooks and headers) is used for all MCP traffic.",
34243448
transports=("streamable-http",),
34253449
note="Only observable over HTTP: the httpx2 client is HTTP-specific.",
34263450
),
@@ -3592,9 +3616,7 @@ def __post_init__(self) -> None:
35923616
),
35933617
"client-auth:403-scope-upgrade": Requirement(
35943618
source=f"{SPEC_BASE_URL}/basic/authorization#step-up-authorization-flow",
3595-
behavior=(
3596-
"A 403 with WWW-Authenticate triggers a scope-upgrade authorization attempt; repeated 403s do not loop."
3597-
),
3619+
behavior="A 403 with WWW-Authenticate triggers a scope-upgrade authorization attempt.",
35983620
transports=("streamable-http",),
35993621
note="OAuth is HTTP-only.",
36003622
),
@@ -3854,8 +3876,7 @@ def __post_init__(self) -> None:
38543876
source=f"{SPEC_2026_BASE_URL}/basic/authorization#authorization-response-validation",
38553877
behavior=(
38563878
"The client validates the RFC 9207 iss authorization-response parameter against the "
3857-
"authorization server issuer (simple string comparison) and rejects a mismatch, or a "
3858-
"missing iss when the server advertises support (SEP-2468)."
3879+
"authorization server issuer (simple string comparison) and rejects a mismatch (SEP-2468)."
38593880
),
38603881
added_in="2026-07-28",
38613882
transports=("streamable-http",),

0 commit comments

Comments
 (0)