Skip to content

Commit b591b58

Browse files
committed
Align interaction-manifest ids with the cross-SDK vocabulary
Rename 14 requirement ids to the names shared with the typescript-sdk e2e suite (sampling:create:*, elicitation:url:action:*, protocol:meta:*, the client-auth stepup/iss/scope/dcr family, the mcpserver context helpers), updating every @requirement decorator to match. The resources:read:unknown-uri name previously sat on an entry whose test proves lowlevel handler-error passthrough, not the unknown-resource rule; that entry is re-described honestly as protocol:error:handler-error- passthrough (source 'sdk'), and the real unknown-resource entry (-32602 with the URI in error.data, SEP-2164) takes the vacated name with its source repointed to the 2026 page that mandates it. The protocol:meta:request-to-handler 2026 arm exclusion now carries the accurate reason (legacy-only-vocabulary) and a note explaining the envelope- key merge that breaks the equality assertion, so the re-admission checklist finds it. Three test docstrings quoting pre-rename spec anchors updated. Cell generation unchanged (830 before and after).
1 parent 957deb2 commit b591b58

11 files changed

Lines changed: 72 additions & 58 deletions

‎tests/interaction/_requirements.py‎

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,13 @@ def __post_init__(self) -> None:
616616
source="sdk",
617617
behavior="Closing the transport fails all in-flight requests with a connection-closed error.",
618618
),
619+
"protocol:error:handler-error-passthrough": Requirement(
620+
source="sdk",
621+
behavior=(
622+
"An MCPError raised by a request handler is returned to the caller as a JSON-RPC error "
623+
"carrying the handler-chosen code and message verbatim."
624+
),
625+
),
619626
"protocol:error:internal-error": Requirement(
620627
source=f"{SPEC_BASE_URL}/basic#responses",
621628
behavior=(
@@ -679,12 +686,23 @@ def __post_init__(self) -> None:
679686
"extension."
680687
),
681688
),
682-
"meta:request-to-handler": Requirement(
689+
"protocol:meta:request-to-handler": Requirement(
683690
source=f"{SPEC_BASE_URL}/basic#_meta",
684691
behavior="The _meta object the client attaches to a request is visible to the server handler.",
685-
arm_exclusions=(ArmExclusion(reason="asserts-legacy-handshake", spec_version="2026-07-28"),),
692+
arm_exclusions=(
693+
ArmExclusion(
694+
reason="legacy-only-vocabulary",
695+
spec_version="2026-07-28",
696+
note=(
697+
"The pass-through itself holds at 2026, but the modern envelope merges the reserved "
698+
"io.modelcontextprotocol/* keys into every request's _meta, so the test's "
699+
"nothing-else-injected equality assertion only holds on the legacy wire; needs an "
700+
"era-aware assertion before re-admission."
701+
),
702+
),
703+
),
686704
),
687-
"meta:result-to-client": Requirement(
705+
"protocol:meta:result-to-client": Requirement(
688706
source=f"{SPEC_BASE_URL}/basic#_meta",
689707
behavior="The _meta object a handler attaches to its result is delivered to the client.",
690708
),
@@ -1081,7 +1099,7 @@ def __post_init__(self) -> None:
10811099
# ═══════════════════════════════════════════════════════════════════════════
10821100
# MCPServer: Context helpers (SDK)
10831101
# ═══════════════════════════════════════════════════════════════════════════
1084-
"mcpserver:context:logging": Requirement(
1102+
"mcpserver:context:log-from-handler": Requirement(
10851103
source="sdk",
10861104
behavior=(
10871105
"The Context logging helpers (debug/info/warning/error) send log message notifications at the "
@@ -1094,7 +1112,7 @@ def __post_init__(self) -> None:
10941112
"Context.report_progress sends a progress notification against the requesting client's progress token."
10951113
),
10961114
),
1097-
"mcpserver:context:elicit": Requirement(
1115+
"mcpserver:context:elicit-from-handler": Requirement(
10981116
source="sdk",
10991117
behavior=(
11001118
"Context.elicit sends a form elicitation built from a typed schema and returns a typed "
@@ -1165,8 +1183,11 @@ def __post_init__(self) -> None:
11651183
behavior="resources/read returns text contents carrying uri, mimeType, and the text.",
11661184
),
11671185
"resources:read:unknown-uri": Requirement(
1168-
source=f"{SPEC_BASE_URL}/server/resources#error-handling",
1169-
behavior="resources/read for an unknown URI returns JSON-RPC error -32002 (resource not found).",
1186+
source=f"{SPEC_2026_BASE_URL}/server/resources#error-handling",
1187+
behavior=(
1188+
"resources/read for a URI matching no registered resource returns JSON-RPC error -32602 "
1189+
"(invalid params) with the requested URI in error.data, per SEP-2164."
1190+
),
11701191
),
11711192
"resources:subscribe": Requirement(
11721193
source=f"{SPEC_BASE_URL}/server/resources#subscriptions",
@@ -1325,13 +1346,6 @@ def __post_init__(self) -> None:
13251346
"by resources/read, receiving the parameters extracted from the requested URI."
13261347
),
13271348
),
1328-
"mcpserver:resource:unknown-uri": Requirement(
1329-
source=f"{SPEC_BASE_URL}/server/resources#error-handling",
1330-
behavior=(
1331-
"resources/read for a URI matching no registered resource returns JSON-RPC error -32602 "
1332-
"(invalid params) with the requested URI in error.data, per SEP-2164."
1333-
),
1334-
),
13351349
# ═══════════════════════════════════════════════════════════════════════════
13361350
# Prompts
13371351
# ═══════════════════════════════════════════════════════════════════════════
@@ -1656,23 +1670,23 @@ def __post_init__(self) -> None:
16561670
"rejects every tool-enabled request before it is sent."
16571671
),
16581672
),
1659-
"sampling:create-message:audio-content": Requirement(
1673+
"sampling:create:audio-content": Requirement(
16601674
source=f"{SPEC_BASE_URL}/client/sampling#audio-content",
16611675
behavior="Sampling messages can carry audio content: base64 data with a mimeType.",
16621676
arm_exclusions=(
16631677
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
16641678
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
16651679
),
16661680
),
1667-
"sampling:create-message:image-content": Requirement(
1681+
"sampling:create:image-content": Requirement(
16681682
source=f"{SPEC_BASE_URL}/client/sampling#image-content",
16691683
behavior="Sampling messages can carry image content: base64 data with a mimeType.",
16701684
arm_exclusions=(
16711685
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
16721686
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
16731687
),
16741688
),
1675-
"sampling:create-message:not-supported": Requirement(
1689+
"sampling:create:not-supported": Requirement(
16761690
source=f"{SPEC_BASE_URL}/client/sampling#capabilities",
16771691
behavior=(
16781692
"A sampling request to a client that did not declare the sampling capability fails with an "
@@ -1960,20 +1974,28 @@ def __post_init__(self) -> None:
19601974
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
19611975
),
19621976
),
1963-
"elicitation:url:basic": Requirement(
1964-
source=f"{SPEC_BASE_URL}/client/elicitation#url-mode-elicitation-requests",
1965-
behavior=(
1966-
"A url-mode elicitation delivers the elicitation id and URL to the client callback exactly as "
1967-
"the server sent them."
1968-
),
1977+
"elicitation:url:action:cancel": Requirement(
1978+
source=f"{SPEC_BASE_URL}/client/elicitation#response-actions",
1979+
behavior="A URL-mode elicitation answered with cancel returns the action with no content.",
19691980
arm_exclusions=(
19701981
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
19711982
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
19721983
),
19731984
),
1974-
"elicitation:url:cancel": Requirement(
1985+
"elicitation:url:action:decline": Requirement(
19751986
source=f"{SPEC_BASE_URL}/client/elicitation#response-actions",
1976-
behavior="A URL-mode elicitation answered with cancel returns the action with no content.",
1987+
behavior="A URL-mode elicitation answered with decline returns the action with no content.",
1988+
arm_exclusions=(
1989+
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
1990+
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
1991+
),
1992+
),
1993+
"elicitation:url:basic": Requirement(
1994+
source=f"{SPEC_BASE_URL}/client/elicitation#url-mode-elicitation-requests",
1995+
behavior=(
1996+
"A url-mode elicitation delivers the elicitation id and URL to the client callback exactly as "
1997+
"the server sent them."
1998+
),
19771999
arm_exclusions=(
19782000
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
19792001
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
@@ -2001,14 +2023,6 @@ def __post_init__(self) -> None:
20012023
removed_in="2026-07-28",
20022024
note="removed in 2026-07-28 (spec PR #2891); notifications/elicitation/complete removed, no replacement.",
20032025
),
2004-
"elicitation:url:decline": Requirement(
2005-
source=f"{SPEC_BASE_URL}/client/elicitation#response-actions",
2006-
behavior="A URL-mode elicitation answered with decline returns the action with no content.",
2007-
arm_exclusions=(
2008-
ArmExclusion(reason="server-initiated-request", transport="streamable-http-stateless"),
2009-
ArmExclusion(reason="server-initiated-request", spec_version="2026-07-28"),
2010-
),
2011-
),
20122026
"elicitation:url:not-supported": Requirement(
20132027
source=f"{SPEC_BASE_URL}/client/elicitation#error-handling",
20142028
behavior=(
@@ -3644,7 +3658,7 @@ def __post_init__(self) -> None:
36443658
transports=("streamable-http",),
36453659
note="OAuth is HTTP-only.",
36463660
),
3647-
"client-auth:403-scope-union": Requirement(
3661+
"client-auth:stepup:scope-union": Requirement(
36483662
source=f"{SPEC_BASE_URL}/basic/authorization#step-up-authorization-flow",
36493663
behavior=(
36503664
"On a 403 insufficient_scope step-up, the re-authorization request carries the union of the "
@@ -3687,7 +3701,7 @@ def __post_init__(self) -> None:
36873701
),
36883702
),
36893703
),
3690-
"client-auth:authorize:offline-access-consent": Requirement(
3704+
"client-auth:scope:offline-access-gate": Requirement(
36913705
source="sdk",
36923706
behavior=(
36933707
"When the authorization server's metadata advertises offline_access in scopes_supported and "
@@ -3721,7 +3735,7 @@ def __post_init__(self) -> None:
37213735
transports=("streamable-http",),
37223736
note="OAuth is HTTP-only.",
37233737
),
3724-
"client-auth:dcr:registration-error-surfaces": Requirement(
3738+
"client-auth:dcr:registration-rejected-error": Requirement(
37253739
source="sdk",
37263740
behavior=(
37273741
"A 400 from the registration endpoint surfaces to the caller as an OAuthRegistrationError "
@@ -3896,7 +3910,7 @@ def __post_init__(self) -> None:
38963910
transports=("streamable-http",),
38973911
note="OAuth is HTTP-only.",
38983912
),
3899-
"client-auth:authorization-response:iss-verify": Requirement(
3913+
"client-auth:iss:mismatch-reject": Requirement(
39003914
source=f"{SPEC_2026_BASE_URL}/basic/authorization#authorization-response-validation",
39013915
behavior=(
39023916
"The client validates the RFC 9207 iss authorization-response parameter against the "

‎tests/interaction/auth/test_authorize_token.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ async def recorded_oauth_flow() -> AsyncIterator[RecordedFlow]:
113113

114114
@requirement("client-auth:pkce:s256")
115115
@requirement("client-auth:resource-parameter")
116-
@requirement("client-auth:authorize:offline-access-consent")
116+
@requirement("client-auth:scope:offline-access-gate")
117117
async def test_the_authorize_url_carries_s256_pkce_and_the_resource_indicator(
118118
recorded_oauth_flow: RecordedFlow,
119119
) -> None:
@@ -187,7 +187,7 @@ async def test_a_mismatched_state_on_the_callback_aborts_the_flow() -> None:
187187
await connect_with_oauth(server, provider=provider, headless=headless).__aenter__()
188188

189189

190-
@requirement("client-auth:authorization-response:iss-verify")
190+
@requirement("client-auth:iss:mismatch-reject")
191191
async def test_a_mismatched_iss_on_the_callback_aborts_the_flow() -> None:
192192
"""A callback whose RFC 9207 iss does not match the authorization server issuer aborts the flow.
193193

‎tests/interaction/auth/test_discovery.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ async def test_when_every_prm_probe_fails_the_client_discovers_as_metadata_at_th
152152
assert result.tools[0].name == "probe"
153153

154154

155-
@requirement("client-auth:dcr:registration-error-surfaces")
155+
@requirement("client-auth:dcr:registration-rejected-error")
156156
async def test_a_400_from_the_registration_endpoint_surfaces_as_a_registration_error() -> None:
157157
"""A 400 from `/register` surfaces as `OAuthRegistrationError` carrying the server's body.
158158

‎tests/interaction/auth/test_lifecycle.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ async def test_a_403_insufficient_scope_triggers_one_reauthorize_with_the_challe
179179
assert counts[("POST", "/token")] == 2
180180

181181

182-
@requirement("client-auth:403-scope-union")
182+
@requirement("client-auth:stepup:scope-union")
183183
async def test_a_403_step_up_re_authorizes_with_the_union_of_prior_and_challenged_scopes() -> None:
184184
"""The step-up re-authorize requests the union of the previously requested and challenged scopes.
185185

‎tests/interaction/lowlevel/test_client_connect.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ async def test_prior_discover_populates_state_with_zero_connect_time_traffic() -
155155
async def test_auto_mode_probes_server_discover_and_adopts_the_result() -> None:
156156
"""`Client(..., mode='auto')` sends `server/discover` first and adopts the returned version and server_info.
157157
158-
Requirement `lifecycle:discover:basic` (spec basic/lifecycle#discover): the probe is a
158+
Requirement `lifecycle:discover:basic` (spec server/discover): the probe is a
159159
single `server/discover` request whose result carries supported versions, capabilities,
160160
the cache-hint fields, and the `_meta` serverInfo stamp, after which the session is
161161
modern-negotiated.
@@ -182,7 +182,7 @@ async def test_auto_mode_probes_server_discover_and_adopts_the_result() -> None:
182182
async def test_auto_mode_retries_discover_once_on_unsupported_protocol_version() -> None:
183183
"""A -32022 from `server/discover` triggers exactly one retry at the highest mutual modern version.
184184
185-
Requirement `lifecycle:discover:retry-on-32022` (spec basic/lifecycle#version-errors): the
185+
Requirement `lifecycle:discover:retry-on-32022` (spec basic/versioning#protocol-version-negotiation): the
186186
client intersects `error.data.supported` with its own modern versions and re-probes once;
187187
the second success is adopted. The server's `server/discover` handler is overridden to fail
188188
the first call and succeed on the second.
@@ -352,7 +352,7 @@ async def list_tools(
352352
async def test_http_protocol_version_header_matches_meta_protocol_version_on_every_post() -> None:
353353
"""On streamable-HTTP, the `MCP-Protocol-Version` header on each POST equals `_meta.protocolVersion` in its body.
354354
355-
Requirement `lifecycle:envelope:header-matches-meta` (spec streamable-http#headers): the
355+
Requirement `lifecycle:envelope:header-matches-meta` (spec streamable-http#protocol-version-header): the
356356
body-derived header and the envelope's protocol version are kept in lockstep so the server's
357357
header-based routing and body-based validation never disagree.
358358
"""

‎tests/interaction/lowlevel/test_elicitation.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ async def answer_url(context: ClientRequestContext, params: types.ElicitRequestP
239239
assert result == snapshot(CallToolResult(content=[TextContent(text="accept content=None")]))
240240

241241

242-
@requirement("elicitation:url:decline")
242+
@requirement("elicitation:url:action:decline")
243243
async def test_elicit_url_decline_returns_no_content(connect: Connect) -> None:
244244
"""A declined URL elicitation returns the decline action to the handler with no content."""
245245

@@ -268,7 +268,7 @@ async def answer_url(context: ClientRequestContext, params: types.ElicitRequestP
268268
assert result == snapshot(CallToolResult(content=[TextContent(text="decline content=None")]))
269269

270270

271-
@requirement("elicitation:url:cancel")
271+
@requirement("elicitation:url:action:cancel")
272272
async def test_elicit_url_cancel_returns_no_content(connect: Connect) -> None:
273273
"""A cancelled URL elicitation returns the cancel action to the handler with no content."""
274274

‎tests/interaction/lowlevel/test_meta.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
pytestmark = pytest.mark.anyio
1919

2020

21-
@requirement("meta:request-to-handler")
21+
@requirement("protocol:meta:request-to-handler")
2222
async def test_request_meta_reaches_handler(connect: Connect) -> None:
2323
"""The _meta object the client attaches to a request arrives at the tool handler unchanged."""
2424
request_meta: RequestParamsMeta = {"example.com/trace": "abc-123"}
@@ -43,7 +43,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara
4343
assert observed_metas == [dict(request_meta)]
4444

4545

46-
@requirement("meta:result-to-client")
46+
@requirement("protocol:meta:result-to-client")
4747
async def test_result_meta_reaches_client(connect: Connect, unstamped: Unstamp) -> None:
4848
"""The _meta object a handler attaches to its result is delivered to the client unchanged."""
4949
result_meta = {"example.com/cost": 3}

‎tests/interaction/lowlevel/test_resources.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ async def read_resource(ctx: ServerRequestContext, params: types.ReadResourceReq
141141
)
142142

143143

144-
@requirement("resources:read:unknown-uri")
144+
@requirement("protocol:error:handler-error-passthrough")
145145
async def test_read_resource_unknown_uri_is_protocol_error(connect: Connect) -> None:
146146
"""A handler that rejects an unrecognised URI with MCPError produces a JSON-RPC error.
147147

‎tests/interaction/lowlevel/test_sampling.py‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ async def sampling_callback(
155155
)
156156

157157

158-
@requirement("sampling:create-message:image-content")
158+
@requirement("sampling:create:image-content")
159159
async def test_create_message_request_with_image_content_reaches_callback(connect: Connect) -> None:
160160
"""A sampling request message carrying image content arrives at the client callback intact.
161161
@@ -207,7 +207,7 @@ async def sampling_callback(
207207
)
208208

209209

210-
@requirement("sampling:create-message:image-content")
210+
@requirement("sampling:create:image-content")
211211
async def test_create_message_result_with_image_content_returns_to_handler(connect: Connect) -> None:
212212
"""A sampling result whose content is an image is returned to the requesting handler intact.
213213
@@ -281,7 +281,7 @@ async def sampling_callback(context: ClientRequestContext, params: CreateMessage
281281
assert result == snapshot(CallToolResult(content=[TextContent(text="-1: User rejected sampling request")]))
282282

283283

284-
@requirement("sampling:create-message:not-supported")
284+
@requirement("sampling:create:not-supported")
285285
async def test_create_message_without_callback_is_error(connect: Connect) -> None:
286286
"""A sampling request to a client with no sampling callback fails with the SDK's default error."""
287287

@@ -437,7 +437,7 @@ async def sampling_callback(
437437
assert captured == snapshot([SamplingCapability()])
438438

439439

440-
@requirement("sampling:create-message:audio-content")
440+
@requirement("sampling:create:audio-content")
441441
async def test_create_message_request_with_audio_content_reaches_callback(connect: Connect) -> None:
442442
"""A sampling request message carrying audio content arrives at the client callback intact.
443443
@@ -489,7 +489,7 @@ async def sampling_callback(
489489
)
490490

491491

492-
@requirement("sampling:create-message:audio-content")
492+
@requirement("sampling:create:audio-content")
493493
async def test_create_message_result_with_audio_content_returns_to_handler(connect: Connect) -> None:
494494
"""A sampling result whose content is audio is returned to the requesting handler intact.
495495

‎tests/interaction/mcpserver/test_context.py‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
pytestmark = pytest.mark.anyio
2828

2929

30-
@requirement("mcpserver:context:logging")
30+
@requirement("mcpserver:context:log-from-handler")
3131
@requirement("logging:capability:declared")
3232
async def test_context_logging_helpers_send_log_notifications(connect: Connect) -> None:
3333
"""Each Context logging helper sends a log message notification at the matching severity.
@@ -121,7 +121,7 @@ async def whoami(ctx: Context) -> str:
121121
assert request_id
122122

123123

124-
@requirement("mcpserver:context:logging")
124+
@requirement("mcpserver:context:log-from-handler")
125125
@requirement("protocol:progress:no-token")
126126
async def test_report_progress_without_a_progress_token_sends_nothing(connect: Connect, unstamped: Unstamp) -> None:
127127
"""When the caller supplied no progress callback, Context.report_progress is a silent no-op.
@@ -153,7 +153,7 @@ async def collect(message: IncomingMessage) -> None:
153153
)
154154

155155

156-
@requirement("mcpserver:context:elicit")
156+
@requirement("mcpserver:context:elicit-from-handler")
157157
@requirement("tools:call:elicitation-roundtrip")
158158
async def test_context_elicit_returns_typed_result(connect: Connect) -> None:
159159
"""Context.elicit sends a form elicitation built from a pydantic schema and returns a typed result.

0 commit comments

Comments
 (0)