Skip to content

Commit 4395a87

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 3c0bfda commit 4395a87

1 file changed

Lines changed: 53 additions & 32 deletions

File tree

‎tests/interaction/_requirements.py‎

Lines changed: 53 additions & 32 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",
@@ -399,7 +404,7 @@ def __post_init__(self) -> None:
399404
source=f"{SPEC_2026_BASE_URL}/server/discover",
400405
behavior=(
401406
"Calling discover() sends server/discover with no params and returns a typed DiscoverResult "
402-
"carrying protocolVersion, capabilities, serverInfo and the cache hint fields."
407+
"carrying supportedVersions, capabilities, serverInfo and the cache hint fields."
403408
),
404409
added_in="2026-07-28",
405410
),
@@ -414,18 +419,25 @@ def __post_init__(self) -> None:
414419
"lifecycle:discover:fallback-method-not-found": Requirement(
415420
source=f"{SPEC_2026_BASE_URL}/basic/transports/stdio#backward-compatibility",
416421
behavior=(
417-
"When server/discover returns any JSON-RPC error or a bare HTTP 4xx, an auto-negotiating "
418-
"client falls back to the legacy initialize handshake and the connection succeeds at a "
419-
"handshake-era version (legacy servers reject the probe with various codes)."
422+
"When server/discover returns a JSON-RPC error that is not a recognized modern negotiation "
423+
"error (-32022 retries or raises instead; see lifecycle:discover:retry-on-32022), or a bare "
424+
"HTTP 4xx, an auto-negotiating client falls back to the legacy initialize handshake and the "
425+
"connection succeeds at a handshake-era version; the fallback is not keyed to specific codes "
426+
"(legacy servers reject the probe with various codes)."
420427
),
421428
added_in="2026-07-28",
429+
note=(
430+
"The SDK keys its no-fallback carve-out to -32022 alone, while the spec's carve-out is any "
431+
"recognized modern JSON-RPC error (an open set); no test drives a modern-error probe "
432+
"rejection other than -32022."
433+
),
422434
),
423435
"lifecycle:discover:network-error-raises": Requirement(
424436
source="sdk",
425437
behavior=(
426438
"A network/connection error during server/discover propagates to the caller without "
427-
"falling back to initialize; any rpc-error or 4xx falls back (legacy servers reject the "
428-
"probe with various codes). An outage is never an era verdict."
439+
"falling back to initialize; fallback is reserved for server rejections (see "
440+
"lifecycle:discover:fallback-method-not-found). An outage is never an era verdict."
429441
),
430442
transports=("streamable-http", "streamable-http-stateless"),
431443
added_in="2026-07-28",
@@ -613,14 +625,15 @@ def __post_init__(self) -> None:
613625
note=(
614626
"The dispatcher drops null-id error responses with a debug log; in v1, JSONRPCError.id was "
615627
"non-nullable, so a null-id error response failed transport validation and the resulting "
616-
"ValidationError was surfaced to message_handler as an exception. A typed fault channel "
617-
"restoring visibility is planned before v2 stable."
628+
"ValidationError was surfaced to message_handler as an exception. The v2 fault channel "
629+
"exists (message_handler receives stream exceptions), but response routing drops the "
630+
"null-id error before anything reaches it."
618631
),
619632
),
620633
deferred=(
621634
"Not yet covered here: the current drop is pinned at the dispatcher level by "
622-
"tests/shared/test_jsonrpc_dispatcher.py; an interaction-level test waits on the planned "
623-
"fault channel."
635+
"tests/shared/test_jsonrpc_dispatcher.py; an interaction-level test waits on the dispatcher "
636+
"routing null-id errors into the existing fault channel."
624637
),
625638
),
626639
"protocol:meta:related-task": Requirement(
@@ -851,6 +864,13 @@ def __post_init__(self) -> None:
851864
"tools:call:structured-content:text-mirror": Requirement(
852865
source=f"{SPEC_BASE_URL}/server/tools#structured-content",
853866
behavior="A tool returning structured content also returns the serialized JSON as a text content block.",
867+
divergence=Divergence(
868+
note=(
869+
"Holds for object returns (the bound test pins the serialized-JSON mirror); a "
870+
"list-returning tool yields one text block per element rather than the serialized JSON "
871+
"of its structured value (pinned by the test on mcpserver:tool:output-schema:wrapped)."
872+
),
873+
),
854874
),
855875
"tools:call:unknown-name": Requirement(
856876
source=f"{SPEC_BASE_URL}/server/tools#error-handling",
@@ -1052,6 +1072,13 @@ def __post_init__(self) -> None:
10521072
"The Context logging helpers (debug/info/warning/error) send log message notifications at the "
10531073
"corresponding severity."
10541074
),
1075+
divergence=Divergence(
1076+
note=(
1077+
"At 2026-07-28 the spec forbids notifications/message for a request whose _meta lacks the "
1078+
"io.modelcontextprotocol/logLevel opt-in; the Context helpers never read that key and emit "
1079+
"unconditionally, so a bound test pins the un-gated delivery on the live 2026-07-28 cells."
1080+
),
1081+
),
10551082
),
10561083
"mcpserver:context:progress": Requirement(
10571084
source="sdk",
@@ -1340,7 +1367,7 @@ def __post_init__(self) -> None:
13401367
behavior="prompts/get for a name that was never registered returns JSON-RPC error -32602 (Invalid params).",
13411368
divergence=Divergence(
13421369
note=(
1343-
"The spec's example uses -32602 Invalid params for unknown prompts; MCPServer raises "
1370+
"The spec SHOULD-lists -32602 Invalid params for an invalid prompt name; MCPServer raises "
13441371
"ValueError, which the low-level server converts to error code 0."
13451372
),
13461373
),
@@ -2027,9 +2054,8 @@ def __post_init__(self) -> None:
20272054
"mcpserver:register:post-connect": Requirement(
20282055
source="sdk",
20292056
behavior=(
2030-
"A tool, resource, or prompt registered or removed after the client connected appears in (or "
2031-
"disappears from) the corresponding list results, and the change is announced with a "
2032-
"list_changed notification."
2057+
"A tool registered or removed after the client connected appears in (or disappears from) "
2058+
"tools/list results, and the change is announced with a list_changed notification."
20332059
),
20342060
divergence=Divergence(
20352061
note=(
@@ -2481,8 +2507,7 @@ def __post_init__(self) -> None:
24812507
"transport:streamable-http:notifications": Requirement(
24822508
source=f"{SPEC_BASE_URL}/basic/transports#streamable-http",
24832509
behavior=(
2484-
"Notifications emitted during a request are delivered on that request's SSE stream and reach "
2485-
"the client's callbacks, in order, before the response."
2510+
"Notifications emitted during a request reach the client's callbacks over the streamable HTTP framing."
24862511
),
24872512
transports=("streamable-http",),
24882513
note="Only observable over streamable HTTP: per-request SSE streams are HTTP-specific.",
@@ -3095,8 +3120,9 @@ def __post_init__(self) -> None:
30953120
"hosting:http:protocol-version-rejection-literal": Requirement(
30963121
source="sdk",
30973122
behavior=(
3098-
"The legacy streamable-HTTP transport's version-rejection body contains the literal substring "
3099-
"'Unsupported protocol version', which other-SDK clients substring-match during negotiation."
3123+
"The streamable-HTTP version-rejection body contains the literal substring 'Unsupported "
3124+
"protocol version', which other-SDK clients substring-match during negotiation; the modern "
3125+
"request classifier is its only emission site."
31003126
),
31013127
transports=("streamable-http",),
31023128
note=(
@@ -3138,17 +3164,18 @@ def __post_init__(self) -> None:
31383164
),
31393165
"hosting:http:modern:initialize-removed": Requirement(
31403166
source=f"{SPEC_2026_BASE_URL}/basic/index",
3141-
behavior="A 2026-07-28 initialize request is answered with METHOD_NOT_FOUND.",
3167+
behavior="A 2026-07-28 initialize request is answered with METHOD_NOT_FOUND at HTTP 404.",
31423168
added_in="2026-07-28",
31433169
transports=("streamable-http",),
31443170
note=("Only observable over streamable HTTP: the modern entry's method registry omits initialize."),
31453171
),
31463172
"hosting:http:modern:legacy-fallthrough": Requirement(
31473173
source=f"{SPEC_2026_BASE_URL}/basic/versioning",
31483174
behavior=(
3149-
"Non-2026-07-28 traffic on the same /mcp endpoint reaches the legacy transport "
3150-
"byte-unchanged: a 2025-era initialize handshake still completes, and an unrecognised "
3151-
"MCP-Protocol-Version header still produces the legacy 400 'Unsupported protocol version' literal."
3175+
"Initialize-handshake-era traffic on the same /mcp endpoint reaches the legacy transport "
3176+
"byte-unchanged: a 2025-era initialize handshake still completes. Any other "
3177+
"MCP-Protocol-Version header routes to the modern entry, whose validation ladder rejects "
3178+
"the envelope-less request with 400 INVALID_PARAMS."
31523179
),
31533180
added_in="2026-07-28",
31543181
transports=("streamable-http",),
@@ -3267,10 +3294,7 @@ def __post_init__(self) -> None:
32673294
),
32683295
"client-transport:http:custom-client": Requirement(
32693296
source="sdk",
3270-
behavior=(
3271-
"A caller-supplied HTTP client (and its event hooks and headers) is used for all MCP traffic, "
3272-
"including auth flows."
3273-
),
3297+
behavior="A caller-supplied HTTP client (and its event hooks and headers) is used for all MCP traffic.",
32743298
transports=("streamable-http",),
32753299
note="Only observable over HTTP: the httpx client is HTTP-specific.",
32763300
),
@@ -3442,9 +3466,7 @@ def __post_init__(self) -> None:
34423466
),
34433467
"client-auth:403-scope-upgrade": Requirement(
34443468
source=f"{SPEC_BASE_URL}/basic/authorization#step-up-authorization-flow",
3445-
behavior=(
3446-
"A 403 with WWW-Authenticate triggers a scope-upgrade authorization attempt; repeated 403s do not loop."
3447-
),
3469+
behavior="A 403 with WWW-Authenticate triggers a scope-upgrade authorization attempt.",
34483470
transports=("streamable-http",),
34493471
note="OAuth is HTTP-only.",
34503472
),
@@ -3691,8 +3713,7 @@ def __post_init__(self) -> None:
36913713
source=f"{SPEC_2026_BASE_URL}/basic/authorization#authorization-response-validation",
36923714
behavior=(
36933715
"The client validates the RFC 9207 iss authorization-response parameter against the "
3694-
"authorization server issuer (simple string comparison) and rejects a mismatch, or a "
3695-
"missing iss when the server advertises support (SEP-2468)."
3716+
"authorization server issuer (simple string comparison) and rejects a mismatch (SEP-2468)."
36963717
),
36973718
added_in="2026-07-28",
36983719
transports=("streamable-http",),

0 commit comments

Comments
 (0)