Files
modelcontextprotocol--pytho…/tests/server/test_request_state_boundary.py
T
wehub-resource-sync 49b9bb6724
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:10:27 +08:00

1298 lines
55 KiB
Python

"""`RequestStateBoundary` end to end: seal outbound, verify and restore inbound, one frozen error on failure."""
import json
import logging
from collections.abc import Awaitable, Callable
from typing import Any, cast
import anyio
import pytest
from mcp_types import (
INTERNAL_ERROR,
INVALID_PARAMS,
CallToolRequestParams,
CallToolResult,
ElicitRequest,
ElicitRequestFormParams,
ElicitResult,
InputRequiredResult,
ListToolsResult,
PaginatedRequestParams,
ReadResourceResult,
RequestParams,
TextContent,
TextResourceContents,
Tool,
)
import mcp.server.request_state as request_state_module
from mcp import Client
from mcp.server import MCPServer, Server, ServerRequestContext
from mcp.server.auth.middleware.auth_context import auth_context_var
from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser
from mcp.server.auth.provider import AccessToken
from mcp.server.context import HandlerResult
from mcp.server.mcpserver import Context
from mcp.server.mcpserver.server import _MISSING_AUDIENCE
from mcp.server.request_state import (
AESGCMRequestStateCodec,
InvalidRequestState,
RequestStateBoundary,
RequestStateSecurity,
)
from mcp.shared.exceptions import MCPError
from .test_runner import connected_runner
pytestmark = pytest.mark.anyio
_KEY = b"0123456789abcdef0123456789abcdef" # 32 bytes
_T0 = 1_782_345_600.0 # frozen mint instant for clock-controlled tests
_TTL = 600.0
def _ask(message: str) -> ElicitRequest:
"""A minimal elicitation request for a manual tool's `input_requests`."""
return ElicitRequest(
params=ElicitRequestFormParams(
message=message,
requested_schema={
"type": "object",
"properties": {"confirm": {"type": "boolean"}},
"required": ["confirm"],
},
)
)
def _accept() -> ElicitResult:
return ElicitResult(action="accept", content={"confirm": True})
async def _list_tools(ctx: ServerRequestContext[Any], params: PaginatedRequestParams | None) -> ListToolsResult:
"""`ClientSession.call_tool` consults tools/list, so lowlevel fixtures must answer it."""
return ListToolsResult(tools=[Tool(name="t", input_schema={"type": "object"})])
class _PassthroughCodec:
"""Cryptography-free codec (the token IS the payload) that puts arbitrary bytes behind a successful unseal."""
def seal(self, payload: bytes) -> str:
return payload.decode()
def unseal(self, token: str) -> bytes:
return token.encode()
class _CustomMethodParams(RequestParams):
"""Params for a custom (non-carrier) method."""
request_state: str | None = None
class _Clock:
"""Stands in for the `time` module inside `mcp.server.request_state`."""
def __init__(self, now: float) -> None:
self.now = now
def time(self) -> float:
return self.now
def _tamper(token: str) -> str:
"""Flip one mid-token character; strict canonical decoding rejects any single-character change."""
i = len(token) // 2
return token[:i] + ("A" if token[i] != "A" else "B") + token[i + 1 :]
def _assert_frozen_rejection(exc: pytest.ExceptionInfo[MCPError]) -> None:
"""Assert the single frozen wire shape for every inbound verification failure."""
assert exc.value.error.code == INVALID_PARAMS
assert exc.value.error.message == "Invalid or expired requestState"
assert exc.value.error.data == {"reason": "invalid_request_state"}
def _manual_server(
security: RequestStateSecurity | None, *, state: str = "awaiting-confirm", name: str = "manual"
) -> tuple[MCPServer, list[str | None]]:
"""MCPServer with one manual MRTR tool: round 1 asks, the retry records the echoed `ctx.request_state`.
`security=None` exercises the default posture (process-local ephemeral sealing), not plaintext.
"""
seen: list[str | None] = []
mcp = MCPServer(name, request_state_security=security)
@mcp.tool()
async def deploy(env: str, ctx: Context) -> str | InputRequiredResult:
if ctx.input_responses is None:
return InputRequiredResult(input_requests={"confirm": _ask(f"Deploy to {env}?")}, request_state=state)
seen.append(ctx.request_state)
return f"deployed to {env}"
return mcp, seen
async def _first_round(client: Client, name: str, args: dict[str, Any]) -> str:
"""Round 1 of the manual loop: call without responses, return the wire token."""
first = await client.session.call_tool(name, args, allow_input_required=True)
assert isinstance(first, InputRequiredResult)
assert first.request_state is not None
return first.request_state
async def _retry(client: Client, name: str, args: dict[str, Any], token: str) -> CallToolResult | InputRequiredResult:
"""The retry round: echo the wire token with the elicited answer attached."""
return await client.session.call_tool(
name, args, input_responses={"confirm": _accept()}, request_state=token, allow_input_required=True
)
# -- end-to-end seal/unseal through the public surfaces -------------------------------
async def test_request_state_is_sealed_on_the_wire_and_restored_for_the_handler() -> None:
"""Spec-mandated (basic/patterns/mrtr server requirements 4-5): the wire carries an
opaque token, never the handler's plaintext, and a faithful echo restores it."""
plaintext = "awaiting-confirm:9f2e"
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY]), state=plaintext)
with anyio.fail_after(5):
async with Client(mcp) as client:
first = await client.session.call_tool("deploy", {"env": "prod"}, allow_input_required=True)
assert isinstance(first, InputRequiredResult)
assert first.request_state is not None
assert first.request_state != plaintext
assert first.request_state.startswith("v1.")
second = await _retry(client, "deploy", {"env": "prod"}, first.request_state)
assert isinstance(second, CallToolResult)
assert not second.is_error
assert isinstance(second.content[0], TextContent)
assert second.content[0].text == "deployed to prod"
assert seen == [plaintext]
async def test_lowlevel_server_gets_identical_sealing_from_the_one_line_middleware_append() -> None:
"""Spec-mandated (basic/patterns/mrtr server requirements 4-5): appending the public
`RequestStateBoundary` to `Server.middleware` gives the lowlevel tier the same sealing."""
plaintext = "lowlevel-round-1"
seen: list[str | None] = []
async def call_tool(
ctx: ServerRequestContext[Any], params: CallToolRequestParams
) -> CallToolResult | InputRequiredResult:
if params.input_responses is None:
return InputRequiredResult(input_requests={"confirm": _ask("Proceed?")}, request_state=plaintext)
seen.append(params.request_state)
return CallToolResult(content=[TextContent(text="done")])
server = Server("srv", on_call_tool=call_tool, on_list_tools=_list_tools)
server.middleware.append(RequestStateBoundary(RequestStateSecurity(keys=[_KEY]), default_audience=server.name))
with anyio.fail_after(5):
async with Client(server) as client:
first = await client.session.call_tool("t", {}, allow_input_required=True)
assert isinstance(first, InputRequiredResult)
assert first.request_state is not None
assert first.request_state != plaintext
assert first.request_state.startswith("v1.")
second = await _retry(client, "t", {}, first.request_state)
assert isinstance(second, CallToolResult)
assert seen == [plaintext]
claims = json.loads(AESGCMRequestStateCodec([_KEY]).unseal(first.request_state))
assert claims["aud"] == "srv"
async def test_a_resource_template_flow_seals_on_resources_read_and_restores_the_plaintext() -> None:
"""Spec-mandated (basic/patterns/mrtr server requirements 4-5): resources/read is an
MRTR carrier, so a template's `requestState` crosses sealed and bound to the uri."""
plaintext = "resource-round-1"
seen: list[str | None] = []
mcp = MCPServer("templated", request_state_security=RequestStateSecurity(keys=[_KEY]))
@mcp.resource("deploy://{env}/confirm")
async def confirm(env: str, ctx: Context) -> str | InputRequiredResult:
if ctx.input_responses is None:
return InputRequiredResult(input_requests={"confirm": _ask(f"Read {env}?")}, request_state=plaintext)
seen.append(ctx.request_state)
return f"confirmed {env}"
with anyio.fail_after(5):
async with Client(mcp) as client:
first = await client.session.read_resource("deploy://prod/confirm", allow_input_required=True)
assert isinstance(first, InputRequiredResult)
assert first.request_state is not None
assert first.request_state != plaintext
assert first.request_state.startswith("v1.")
second = await client.session.read_resource(
"deploy://prod/confirm",
input_responses={"confirm": _accept()},
request_state=first.request_state,
allow_input_required=True,
)
assert isinstance(second, ReadResourceResult)
assert isinstance(second.contents[0], TextResourceContents)
assert second.contents[0].text == "confirmed prod"
claims = json.loads(AESGCMRequestStateCodec([_KEY]).unseal(first.request_state))
assert (claims["m"], claims["t"], claims["s"]) == ("resources/read", "deploy://prod/confirm", plaintext)
assert seen == [plaintext]
# -- verification failures: tamper, expiry, future skew -------------------------------
async def test_tampered_request_state_is_rejected_with_the_frozen_wire_error() -> None:
"""Spec-mandated (basic/patterns/mrtr server requirement 5): a modified echo is
rejected with the frozen -32602 shape and the handler never runs."""
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY]))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, _tamper(token))
_assert_frozen_rejection(exc)
assert seen == []
async def test_expired_request_state_is_rejected_and_just_inside_ttl_is_accepted(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Spec-mandated (basic/patterns/mrtr server requirements 4-5): one second past `ttl`
is rejected, one second inside completes."""
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY], ttl=_TTL))
clock = _Clock(_T0)
monkeypatch.setattr(request_state_module, "time", clock)
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"}) # minted at _T0
clock.now = _T0 + _TTL + 1
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, token)
clock.now = _T0 + _TTL - 1
second = await _retry(client, "deploy", {"env": "prod"}, token)
_assert_frozen_rejection(exc)
assert isinstance(second, CallToolResult)
assert seen == ["awaiting-confirm"]
async def test_state_minted_in_the_future_is_rejected_beyond_the_sixty_second_skew(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Spec-mandated (basic/patterns/mrtr server requirements 4-5): a token minted 120 s
ahead of the verifier's clock is rejected, 30 s ahead is inside the skew allowance."""
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY], ttl=_TTL))
clock = _Clock(_T0)
monkeypatch.setattr(request_state_module, "time", clock)
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"}) # minted at _T0
clock.now = _T0 - 120
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, token)
clock.now = _T0 - 30
second = await _retry(client, "deploy", {"env": "prod"}, token)
_assert_frozen_rejection(exc)
assert isinstance(second, CallToolResult)
assert seen == ["awaiting-confirm"]
# -- request binding -------------------------------------------------------------------
async def test_round_one_state_replayed_on_a_different_tool_is_rejected() -> None:
"""Spec-mandated (basic/patterns/mrtr server requirement 4): a token minted for tool
A is rejected when echoed on tool B of the same server."""
seen: list[str | None] = []
def make_tool(state: str) -> Callable[[Context], Awaitable[str | InputRequiredResult]]:
async def tool(ctx: Context) -> str | InputRequiredResult:
if ctx.input_responses is None:
return InputRequiredResult(input_requests={"confirm": _ask(state)}, request_state=state)
seen.append(ctx.request_state)
return "done"
return tool
mcp = MCPServer("two-tools", request_state_security=RequestStateSecurity(keys=[_KEY]))
mcp.tool(name="alpha")(make_tool("alpha-state"))
mcp.tool(name="beta")(make_tool("beta-state"))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "alpha", {})
with pytest.raises(MCPError) as exc:
await _retry(client, "beta", {}, token)
second = await _retry(client, "alpha", {}, token)
_assert_frozen_rejection(exc)
assert isinstance(second, CallToolResult)
assert seen == ["alpha-state"]
async def test_retry_with_different_arguments_is_rejected_and_the_original_arguments_complete() -> None:
"""Spec-mandated (basic/patterns/mrtr server requirement 4): the same tool retried
with different arguments is rejected."""
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY]))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "staging"}, token)
second = await _retry(client, "deploy", {"env": "prod"}, token)
_assert_frozen_rejection(exc)
assert isinstance(second, CallToolResult)
assert seen == ["awaiting-confirm"]
# -- principal binding -----------------------------------------------------------------
async def test_state_minted_with_a_principal_is_rejected_when_the_verifier_derives_none() -> None:
"""Spec-mandated (basic/patterns/mrtr server requirement 4): state sealed for a
principal is rejected when the verifying round derives none."""
principal: list[str | None] = ["alice"]
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY], bind_principal=lambda ctx: principal[0]))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
principal[0] = None
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, token)
_assert_frozen_rejection(exc)
assert seen == []
async def test_state_minted_without_a_principal_is_rejected_when_the_verifier_derives_one() -> None:
"""Spec-mandated (basic/patterns/mrtr server requirement 4): unbound state is
rejected once the verifying round derives a principal."""
principal: list[str | None] = [None]
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY], bind_principal=lambda ctx: principal[0]))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
principal[0] = "alice"
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, token)
_assert_frozen_rejection(exc)
assert seen == []
async def test_state_for_a_different_principal_is_rejected_and_the_same_principal_completes() -> None:
"""Spec-mandated (basic/patterns/mrtr server requirement 4): one principal's token is
rejected when echoed by another and accepted when the same principal returns."""
principal: list[str | None] = ["alice"]
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY], bind_principal=lambda ctx: principal[0]))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
principal[0] = "bob"
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, token)
principal[0] = "alice"
second = await _retry(client, "deploy", {"env": "prod"}, token)
_assert_frozen_rejection(exc)
assert isinstance(second, CallToolResult)
assert seen == ["awaiting-confirm"]
async def test_a_principal_binding_that_raises_fails_the_seal_as_an_internal_error(
caplog: pytest.LogCaptureFixture,
) -> None:
"""SDK-defined: a raising `bind_principal` fails the seal as a bare internal error, not an unbound mint."""
def boom(ctx: ServerRequestContext[Any, Any]) -> str | None:
raise RuntimeError("identity provider down")
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY], bind_principal=boom))
with anyio.fail_after(5):
async with Client(mcp) as client:
with pytest.raises(MCPError) as exc:
await client.session.call_tool("deploy", {"env": "prod"}, allow_input_required=True)
assert exc.value.error.code == INTERNAL_ERROR
assert exc.value.error.message == "Internal error"
assert exc.value.error.data is None # the reason never reaches the wire
assert seen == []
assert any(r.exc_info is not None and r.exc_info[0] is RuntimeError for r in caplog.records)
async def test_a_principal_binding_that_raises_fails_the_unseal_with_the_frozen_rejection(
caplog: pytest.LogCaptureFixture,
) -> None:
"""SDK-defined: a `bind_principal` that raises while verifying collapses to the frozen rejection."""
rounds: list[int] = []
def flaky(ctx: ServerRequestContext[Any, Any]) -> str | None:
rounds.append(1)
if len(rounds) == 1:
return "alice" # mint round succeeds
raise RuntimeError("identity provider down") # verify round raises
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY], bind_principal=flaky))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, token)
_assert_frozen_rejection(exc)
assert seen == []
assert any(r.exc_info is not None and r.exc_info[0] is RuntimeError for r in caplog.records)
async def test_two_mints_for_the_same_principal_carry_different_salted_principal_claims() -> None:
"""SDK-defined: the `p` claim is salted per mint, so two tokens for the same principal are not linkable."""
mcp, _ = _manual_server(RequestStateSecurity(keys=[_KEY], bind_principal=lambda ctx: "alice"))
with anyio.fail_after(5):
async with Client(mcp) as client:
token_one = await _first_round(client, "deploy", {"env": "prod"})
token_two = await _first_round(client, "deploy", {"env": "prod"})
codec = AESGCMRequestStateCodec([_KEY])
claims_one = json.loads(codec.unseal(token_one))
claims_two = json.loads(codec.unseal(token_two))
assert "p" in claims_one
assert "p" in claims_two
assert claims_one["p"] != claims_two["p"]
# -- audience binding ------------------------------------------------------------------
async def test_two_servers_sharing_a_key_reject_each_others_state_via_the_name_audience() -> None:
"""SDK-defined: the server name is the default audience, so servers sharing a key reject each other's state."""
mcp_billing, seen_billing = _manual_server(RequestStateSecurity(keys=[_KEY]), name="billing")
mcp_payments, seen_payments = _manual_server(RequestStateSecurity(keys=[_KEY]), name="payments")
with anyio.fail_after(5):
async with Client(mcp_billing) as billing, Client(mcp_payments) as payments:
token = await _first_round(billing, "deploy", {"env": "prod"})
with pytest.raises(MCPError) as exc:
await _retry(payments, "deploy", {"env": "prod"}, token)
second = await _retry(billing, "deploy", {"env": "prod"}, token)
_assert_frozen_rejection(exc)
assert isinstance(second, CallToolResult)
assert seen_billing == ["awaiting-confirm"]
assert seen_payments == []
async def test_audience_presence_drift_is_rejected_in_both_directions() -> None:
"""SDK-defined: audience presence drift is rejected in both directions; each boundary accepts its own mint."""
def make_server(boundary: RequestStateBoundary) -> Server:
async def call_tool(
ctx: ServerRequestContext[Any], params: CallToolRequestParams
) -> CallToolResult | InputRequiredResult:
if params.input_responses is None:
return InputRequiredResult(input_requests={"confirm": _ask("Go?")}, request_state="round-1")
return CallToolResult(content=[TextContent(text="done")])
server = Server("srv", on_call_tool=call_tool, on_list_tools=_list_tools)
server.middleware.append(boundary)
return server
security = RequestStateSecurity(keys=[_KEY])
bound = make_server(RequestStateBoundary(security, default_audience="svc"))
unbound = make_server(RequestStateBoundary(security, default_audience=None))
with anyio.fail_after(5):
async with Client(bound) as on_bound, Client(unbound) as on_unbound:
bound_token = await _first_round(on_bound, "t", {})
unbound_token = await _first_round(on_unbound, "t", {})
with pytest.raises(MCPError) as bound_state_on_unbound:
await _retry(on_unbound, "t", {}, bound_token)
with pytest.raises(MCPError) as unbound_state_on_bound:
await _retry(on_bound, "t", {}, unbound_token)
assert isinstance(await _retry(on_bound, "t", {}, bound_token), CallToolResult)
assert isinstance(await _retry(on_unbound, "t", {}, unbound_token), CallToolResult)
_assert_frozen_rejection(bound_state_on_unbound)
_assert_frozen_rejection(unbound_state_on_bound)
async def test_an_explicit_policy_audience_overrides_the_server_name_default() -> None:
"""SDK-defined: `RequestStateSecurity(audience=...)` overrides the server-name default."""
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY], audience="prod-fleet"), name="one-box")
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
second = await _retry(client, "deploy", {"env": "prod"}, token)
claims = json.loads(AESGCMRequestStateCodec([_KEY]).unseal(token))
assert claims["aud"] == "prod-fleet"
assert isinstance(second, CallToolResult)
assert seen == ["awaiting-confirm"]
# -- claims envelope (white-box through the public codec) -----------------------------
async def test_claims_envelope_carries_the_documented_fields_and_omits_p_when_unbound() -> None:
"""SDK-defined: the sealed payload is the documented claims JSON; no `p` claim when the principal is None."""
plaintext = "step-one"
mcp, _ = _manual_server(
RequestStateSecurity(keys=[_KEY], ttl=_TTL, bind_principal=lambda ctx: None), state=plaintext
)
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
claims = json.loads(AESGCMRequestStateCodec([_KEY]).unseal(token))
assert set(claims) == {"v", "iat", "exp", "m", "t", "a", "s", "aud"}
assert claims["v"] == 1
assert claims["exp"] == claims["iat"] + int(_TTL)
assert claims["m"] == "tools/call"
assert claims["t"] == "deploy"
assert isinstance(claims["a"], str) and claims["a"]
assert claims["aud"] == "manual" # the MCPServer name, the boundary's default audience
assert claims["s"] == plaintext
async def test_each_round_is_resealed_with_a_fresh_token_and_a_restamped_iat(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""SDK-defined: every round reseals with a fresh token and `iat`, so `ttl` bounds per-round think time."""
mcp = MCPServer("wizard-server", request_state_security=RequestStateSecurity(keys=[_KEY], ttl=_TTL))
@mcp.tool()
async def wizard(ctx: Context) -> str | InputRequiredResult:
if ctx.input_responses is None:
return InputRequiredResult(input_requests={"first": _ask("First?")}, request_state="step-1")
if ctx.request_state == "step-1":
return InputRequiredResult(input_requests={"second": _ask("Second?")}, request_state="step-2")
return "done"
clock = _Clock(_T0)
monkeypatch.setattr(request_state_module, "time", clock)
with anyio.fail_after(5):
async with Client(mcp) as client:
first = await client.session.call_tool("wizard", {}, allow_input_required=True)
assert isinstance(first, InputRequiredResult)
assert first.request_state is not None
clock.now = _T0 + 5
second = await client.session.call_tool(
"wizard",
{},
input_responses={"first": _accept()},
request_state=first.request_state,
allow_input_required=True,
)
assert isinstance(second, InputRequiredResult)
assert second.request_state is not None
third = await client.session.call_tool(
"wizard",
{},
input_responses={"second": _accept()},
request_state=second.request_state,
allow_input_required=True,
)
assert isinstance(third, CallToolResult)
assert first.request_state != second.request_state
codec = AESGCMRequestStateCodec([_KEY])
claims_one = json.loads(codec.unseal(first.request_state))
claims_two = json.loads(codec.unseal(second.request_state))
assert claims_two["iat"] >= claims_one["iat"]
assert (claims_one["iat"], claims_two["iat"]) == (int(_T0), int(_T0) + 5)
# -- the default posture: every MCPServer seals under an ephemeral policy ---------------
async def test_an_mcpserver_seals_request_state_by_default() -> None:
"""SDK-defined: with no `request_state_security=`, an MCPServer seals under a process-local key."""
plaintext = "plain-wizard-state"
mcp, seen = _manual_server(None, state=plaintext)
with anyio.fail_after(5):
async with Client(mcp) as client:
first = await client.session.call_tool("deploy", {"env": "prod"}, allow_input_required=True)
assert isinstance(first, InputRequiredResult)
assert first.request_state is not None
assert first.request_state != plaintext
assert first.request_state.startswith("v1.")
with pytest.raises(MCPError) as fabricated:
await _retry(client, "deploy", {"env": "prod"}, plaintext)
second = await _retry(client, "deploy", {"env": "prod"}, first.request_state)
_assert_frozen_rejection(fabricated)
assert isinstance(second, CallToolResult)
assert seen == [plaintext]
async def test_the_default_key_is_per_instance_so_servers_never_cross_accept() -> None:
"""SDK-defined: each default MCPServer mints its own ephemeral key; another instance rejects its state."""
one, seen_one = _manual_server(None)
two, seen_two = _manual_server(None)
with anyio.fail_after(5):
async with Client(one) as on_one, Client(two) as on_two:
token = await _first_round(on_one, "deploy", {"env": "prod"})
with pytest.raises(MCPError) as exc:
await _retry(on_two, "deploy", {"env": "prod"}, token)
second = await _retry(on_one, "deploy", {"env": "prod"}, token)
_assert_frozen_rejection(exc)
assert isinstance(second, CallToolResult)
assert seen_one == ["awaiting-confirm"]
assert seen_two == []
async def test_a_boundary_free_lowlevel_server_passes_request_state_through_verbatim() -> None:
"""SDK-defined: without a boundary in `Server.middleware`, `requestState` crosses as the handler's plaintext."""
plaintext = "lowlevel-plain-round-1"
seen: list[str | None] = []
async def call_tool(
ctx: ServerRequestContext[Any], params: CallToolRequestParams
) -> CallToolResult | InputRequiredResult:
if params.input_responses is None:
return InputRequiredResult(input_requests={"confirm": _ask("Proceed?")}, request_state=plaintext)
seen.append(params.request_state)
return CallToolResult(content=[TextContent(text="done")])
server = Server("srv", on_call_tool=call_tool, on_list_tools=_list_tools)
with anyio.fail_after(5):
async with Client(server) as client:
first = await client.session.call_tool("t", {}, allow_input_required=True)
assert isinstance(first, InputRequiredResult)
assert first.request_state == plaintext
second = await _retry(client, "t", {}, plaintext)
assert isinstance(second, CallToolResult)
assert seen == [plaintext]
# -- malformed wire input --------------------------------------------------------------
async def test_non_string_inbound_request_state_is_rejected_with_the_frozen_error() -> None:
"""Spec-mandated (basic/patterns/mrtr server requirement 5): a non-string
`requestState` fails at the boundary with the frozen shape."""
calls: list[str] = []
async def call_tool(ctx: ServerRequestContext[Any], params: CallToolRequestParams) -> CallToolResult:
calls.append(params.name)
return CallToolResult(content=[TextContent(text="ran")])
server = Server("srv", on_call_tool=call_tool)
server.middleware.append(RequestStateBoundary(RequestStateSecurity(keys=[_KEY]), default_audience=None))
async with connected_runner(server) as (client, _):
with pytest.raises(MCPError) as exc:
await client.send_raw_request("tools/call", {"name": "t", "arguments": {}, "requestState": 123})
assert calls == []
result = await client.send_raw_request("tools/call", {"name": "t", "arguments": {}})
_assert_frozen_rejection(exc)
assert result["content"][0]["text"] == "ran"
assert calls == ["t"]
@pytest.mark.parametrize(
"install_boundary",
[
pytest.param(True, id="boundary-installed"),
pytest.param(False, id="no-boundary"),
],
)
async def test_an_explicit_null_request_state_is_treated_as_absent(install_boundary: bool) -> None:
"""SDK-defined: an explicit `"requestState": null` is the field's absence, so the handler runs and sees None."""
seen: list[str | None] = []
async def call_tool(ctx: ServerRequestContext[Any], params: CallToolRequestParams) -> CallToolResult:
seen.append(params.request_state)
return CallToolResult(content=[TextContent(text="ran")])
server = Server("srv", on_call_tool=call_tool)
if install_boundary:
server.middleware.append(RequestStateBoundary(RequestStateSecurity(keys=[_KEY]), default_audience=None))
async with connected_runner(server) as (client, _):
result = await client.send_raw_request("tools/call", {"name": "t", "arguments": {}, "requestState": None})
assert result["content"][0]["text"] == "ran"
assert seen == [None]
# -- boundary scope: only the three carrier methods are touched -------------------------
async def test_inbound_request_state_on_a_non_carrier_method_passes_through_unverified() -> None:
"""SDK-defined: only the MRTR carriers are touched; a custom method's `requestState` arrives as sent."""
calls: list[str] = []
async def custom(ctx: ServerRequestContext[Any], params: _CustomMethodParams) -> dict[str, Any]:
calls.append(params.request_state or "fresh")
return {"resultType": "complete"}
server = Server("srv", on_list_tools=_list_tools)
server.add_request_handler("example/mrtr", _CustomMethodParams, custom)
server.middleware.append(RequestStateBoundary(RequestStateSecurity(keys=[_KEY]), default_audience=None))
async with connected_runner(server) as (client, _):
ok = await client.send_raw_request("example/mrtr", {"requestState": "CLIENT-SENT-VALUE"})
fresh = await client.send_raw_request("example/mrtr", {})
assert ok == {"resultType": "complete"}
assert fresh == {"resultType": "complete"}
assert calls == ["CLIENT-SENT-VALUE", "fresh"]
async def test_outbound_request_state_on_a_non_carrier_method_is_not_sealed() -> None:
"""SDK-defined: an input_required result on a custom method keeps its `requestState` unsealed."""
async def custom(ctx: ServerRequestContext[Any], params: _CustomMethodParams) -> InputRequiredResult:
return InputRequiredResult(input_requests={"confirm": _ask("?")}, request_state="ext-handler-plaintext")
server = Server("srv", on_list_tools=_list_tools)
server.add_request_handler("example/mrtr", _CustomMethodParams, custom)
server.middleware.append(RequestStateBoundary(RequestStateSecurity(keys=[_KEY]), default_audience=None))
async with connected_runner(server) as (client, _):
result = await client.send_raw_request("example/mrtr", {})
assert result["resultType"] == "input_required"
assert result["requestState"] == "ext-handler-plaintext"
async def test_an_off_set_input_required_result_without_state_passes_through_untouched() -> None:
"""SDK-defined: an input_required result on a non-carrier method minting no state crosses unmodified."""
async def custom(ctx: ServerRequestContext[Any], params: _CustomMethodParams) -> InputRequiredResult:
return InputRequiredResult(input_requests={"confirm": _ask("?")})
server = Server("srv", on_list_tools=_list_tools)
server.add_request_handler("example/mrtr", _CustomMethodParams, custom)
server.middleware.append(RequestStateBoundary(RequestStateSecurity(keys=[_KEY]), default_audience=None))
async with connected_runner(server) as (client, _):
result = await client.send_raw_request("example/mrtr", {})
assert result["resultType"] == "input_required"
assert "confirm" in result["inputRequests"]
assert "requestState" not in result
# -- custom codec: deny on error -------------------------------------------------------
async def test_a_codec_that_raises_unexpectedly_fails_closed_with_the_frozen_error(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Spec-mandated (basic/patterns/mrtr server requirement 5): a codec that raises
unexpectedly denies with the frozen rejection."""
class ExplodingCodec:
def seal(self, payload: bytes) -> str:
return "opaque-token"
def unseal(self, token: str) -> bytes:
raise RuntimeError("codec exploded")
mcp, seen = _manual_server(RequestStateSecurity(codec=ExplodingCodec()))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
assert token == "opaque-token"
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, token)
_assert_frozen_rejection(exc)
assert seen == []
assert any(r.exc_info is not None and r.exc_info[0] is RuntimeError for r in caplog.records)
async def test_a_codec_reject_reason_reaches_the_log_but_never_the_wire(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Spec-mandated (basic/patterns/mrtr server requirement 5): a custom codec's
`InvalidRequestState` reason is logged server-side, never sent on the wire."""
class RefusingCodec:
def seal(self, payload: bytes) -> str:
return "opaque-token"
def unseal(self, token: str) -> bytes:
raise InvalidRequestState("boom")
mcp, seen = _manual_server(RequestStateSecurity(codec=RefusingCodec()))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, token)
_assert_frozen_rejection(exc)
assert "boom" in caplog.text
assert seen == []
@pytest.mark.parametrize(
"payload",
[
pytest.param("not a claims envelope", id="not-json"),
pytest.param(json.dumps({"v": 1, "iat": 1, "exp": 2}), id="json-missing-claims"),
pytest.param(json.dumps({"v": 2, "iat": 1, "exp": 2, "s": "x"}), id="json-wrong-envelope-version"),
pytest.param(json.dumps({"v": 1, "iat": 1, "exp": 2, "s": 7}), id="json-non-string-state"),
],
)
async def test_codec_authenticated_bytes_that_are_not_a_claims_envelope_are_rejected(payload: str) -> None:
"""SDK-defined: codec-authenticated bytes that are not the claims envelope collapse to the frozen rejection."""
mcp, seen = _manual_server(RequestStateSecurity(codec=_PassthroughCodec(), bind_principal=None))
with anyio.fail_after(5):
async with Client(mcp) as client:
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, payload)
_assert_frozen_rejection(exc)
assert seen == []
async def test_a_forged_principal_claim_that_is_not_base64_is_rejected() -> None:
"""SDK-defined: a `p` claim that does not decode as base64 collapses to the frozen rejection."""
mcp, seen = _manual_server(RequestStateSecurity(codec=_PassthroughCodec(), bind_principal=lambda ctx: "alice"))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
claims = json.loads(token) # passthrough codec: the token IS the envelope JSON
claims["p"] = "A" # a single base64 char can never pad to a valid quantum
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, json.dumps(claims))
_assert_frozen_rejection(exc)
assert seen == []
@pytest.mark.parametrize("forged", [pytest.param(7, id="int"), pytest.param({"x": 1}, id="object")])
async def test_a_non_string_principal_claim_is_rejected_with_the_frozen_error(forged: Any) -> None:
"""SDK-defined: a non-string `p` claim inside a validly-sealed envelope collapses to the frozen rejection."""
mcp, seen = _manual_server(RequestStateSecurity(codec=_PassthroughCodec(), bind_principal=lambda ctx: "alice"))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
claims = json.loads(token) # passthrough codec: the token IS the envelope JSON
claims["p"] = forged
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, json.dumps(claims))
_assert_frozen_rejection(exc)
assert seen == []
# -- log secrecy and the cause-invariant wire error ------------------------------------
async def test_the_wire_error_never_varies_by_cause_and_logs_never_leak_secrets(
monkeypatch: pytest.MonkeyPatch,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Spec-mandated (basic/patterns/mrtr server requirement 5): tampered, expired, and rebound
echoes get identical wire errors, with reasons logged but no secrets in any record."""
plaintext = "secret-plaintext-state-1f9b"
principal = "principal-alice-7c3d"
mcp, seen = _manual_server(
RequestStateSecurity(keys=[_KEY], ttl=_TTL, bind_principal=lambda ctx: principal), state=plaintext
)
clock = _Clock(_T0)
monkeypatch.setattr(request_state_module, "time", clock)
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
with pytest.raises(MCPError) as tampered:
await _retry(client, "deploy", {"env": "prod"}, _tamper(token))
clock.now = _T0 + _TTL + 1
with pytest.raises(MCPError) as expired:
await _retry(client, "deploy", {"env": "prod"}, token)
clock.now = _T0
with pytest.raises(MCPError) as rebound:
await _retry(client, "deploy", {"env": "staging"}, token)
_assert_frozen_rejection(tampered)
shapes = [(e.value.error.code, e.value.error.message, e.value.error.data) for e in (tampered, expired, rebound)]
assert shapes[0] == shapes[1] == shapes[2]
assert seen == []
reject_logs = [r for r in caplog.records if r.name == "mcp.server.request_state" and r.levelno == logging.WARNING]
assert len(reject_logs) == 3
for record in caplog.records:
message = record.getMessage()
assert token not in message
assert plaintext not in message
assert principal not in message
# -- pass-through inertness ------------------------------------------------------------
async def test_a_complete_result_crosses_the_boundary_untouched() -> None:
"""SDK-defined: a complete tools/call wire result passes the boundary as the identical object."""
boundary = RequestStateBoundary(RequestStateSecurity(keys=[_KEY], bind_principal=None), default_audience=None)
complete: dict[str, Any] = {"resultType": "complete", "content": []}
async def call_next(ctx: ServerRequestContext[Any, Any]) -> HandlerResult:
return complete
ctx = ServerRequestContext(
session=cast("Any", None),
lifespan_context={},
protocol_version="2026-07-28",
method="tools/call",
params={"name": "t", "arguments": {}},
)
assert await boundary(ctx, call_next) is complete
async def test_input_required_without_request_state_is_untouched() -> None:
"""SDK-defined: an `input_required` result that asks without minting state crosses the boundary unmodified."""
seen: list[str | None] = []
mcp = MCPServer("stateless-ask", request_state_security=RequestStateSecurity(keys=[_KEY]))
@mcp.tool()
async def ask(ctx: Context) -> str | InputRequiredResult:
if ctx.input_responses is None:
return InputRequiredResult(input_requests={"confirm": _ask("Sure?")})
seen.append(ctx.request_state)
return "done"
with anyio.fail_after(5):
async with Client(mcp) as client:
first = await client.session.call_tool("ask", {}, allow_input_required=True)
assert isinstance(first, InputRequiredResult)
assert first.request_state is None
second = await client.session.call_tool(
"ask", {}, input_responses={"confirm": _accept()}, allow_input_required=True
)
assert isinstance(second, CallToolResult)
assert seen == [None]
async def test_an_input_required_mapping_with_a_non_string_state_is_not_sealed() -> None:
"""SDK-defined: a non-string `requestState` in a wire mapping is not this module's mint; it crosses unchanged."""
boundary = RequestStateBoundary(RequestStateSecurity(keys=[_KEY], bind_principal=None), default_audience=None)
malformed: dict[str, Any] = {"resultType": "input_required", "inputRequests": {}, "requestState": 7}
async def call_next(ctx: ServerRequestContext[Any, Any]) -> HandlerResult:
return malformed
ctx = ServerRequestContext(
session=cast("Any", None),
lifespan_context={},
protocol_version="2026-07-28",
method="tools/call",
params={"name": "t", "arguments": {}},
)
assert await boundary(ctx, call_next) is malformed
async def test_a_notification_crosses_the_boundary_unharmed() -> None:
"""SDK-defined: the boundary is inert for notifications."""
boundary = RequestStateBoundary(RequestStateSecurity(keys=[_KEY], bind_principal=None), default_audience=None)
forwarded: list[ServerRequestContext[Any, Any]] = []
async def call_next(ctx: ServerRequestContext[Any, Any]) -> HandlerResult:
forwarded.append(ctx)
return None
ctx = ServerRequestContext(
session=cast("Any", None),
lifespan_context={},
protocol_version="2026-07-28",
method="notifications/progress",
params={"progressToken": "p", "progress": 1},
)
assert await boundary(ctx, call_next) is None
assert len(forwarded) == 1
assert forwarded[0] is ctx
async def test_a_non_mrtr_method_with_no_params_is_untouched() -> None:
"""SDK-defined: a non-carrier method with absent params passes the boundary inert."""
boundary = RequestStateBoundary(RequestStateSecurity(keys=[_KEY], bind_principal=None), default_audience=None)
listing: dict[str, Any] = {"tools": [], "resultType": "complete"}
async def call_next(ctx: ServerRequestContext[Any, Any]) -> HandlerResult:
return listing
ctx = ServerRequestContext(
session=cast("Any", None),
lifespan_context={},
protocol_version="2026-07-28",
method="tools/list",
params=None,
)
assert await boundary(ctx, call_next) is listing
# -- direct chain invocation: the model-path seal --------------------------------------
async def test_a_short_circuited_input_required_model_is_sealed_via_the_model_path() -> None:
"""SDK-defined: a short-circuited `InputRequiredResult` model is sealed via the model path, on a copy."""
boundary = RequestStateBoundary(RequestStateSecurity(keys=[_KEY], bind_principal=None), default_audience=None)
interim = InputRequiredResult(input_requests={"confirm": _ask("Go?")}, request_state="model-plaintext")
async def call_next(ctx: ServerRequestContext[Any, Any]) -> HandlerResult:
return interim
ctx = ServerRequestContext(
session=cast("Any", None),
lifespan_context={},
protocol_version="2026-07-28",
method="tools/call",
params={"name": "shortcut", "arguments": {}},
)
result = await boundary(ctx, call_next)
assert isinstance(result, InputRequiredResult)
assert result.input_requests == interim.input_requests
assert result.request_state is not None
assert result.request_state != "model-plaintext"
assert result.request_state.startswith("v1.")
claims = json.loads(AESGCMRequestStateCodec([_KEY]).unseal(result.request_state))
assert (claims["m"], claims["t"], claims["s"]) == ("tools/call", "shortcut", "model-plaintext")
assert interim.request_state == "model-plaintext"
# -- user-supplied code on the seal path fails closed -----------------------------------
class _RaisingSealCodec:
"""Codec whose seal always fails, standing in for a KMS outage in a custom codec."""
def seal(self, payload: bytes) -> str:
raise RuntimeError("kms unreachable at 10.0.0.7: wrapped-key-id-42")
def unseal(self, token: str) -> bytes:
raise InvalidRequestState("never minted")
async def test_a_codec_that_raises_during_seal_yields_a_sanitized_internal_error() -> None:
"""SDK-defined: a raising custom codec fails the round with a sanitized internal error, never its own text."""
mcp, _ = _manual_server(RequestStateSecurity(codec=_RaisingSealCodec()))
with anyio.fail_after(5):
async with Client(mcp) as client:
with pytest.raises(MCPError) as exc:
await client.session.call_tool("deploy", {"env": "prod"}, allow_input_required=True)
# The unseal direction of the same broken codec still maps to the frozen rejection.
with pytest.raises(MCPError) as inbound:
await _retry(client, "deploy", {"env": "prod"}, "token-this-codec-never-minted")
assert exc.value.error.code == INTERNAL_ERROR
assert exc.value.error.message == "Internal error"
_assert_frozen_rejection(inbound)
_assert_frozen_rejection(inbound)
async def test_a_non_string_principal_fails_closed_when_sealing() -> None:
"""SDK-defined: a bind_principal returning a non-string denies the round with the sanitized internal error."""
def numeric_user_id(ctx: ServerRequestContext[Any, Any]) -> str:
return cast("str", 12345)
mcp, _ = _manual_server(RequestStateSecurity(keys=[_KEY], bind_principal=numeric_user_id))
with anyio.fail_after(5):
async with Client(mcp) as client:
with pytest.raises(MCPError) as exc:
await client.session.call_tool("deploy", {"env": "prod"}, allow_input_required=True)
assert exc.value.error.code == INTERNAL_ERROR
assert exc.value.error.message == "Internal error"
async def test_a_non_string_principal_fails_closed_when_verifying() -> None:
"""SDK-defined: a non-string principal on the verify side rejects with the frozen error, not a crash."""
principal: list[Any] = ["alice"]
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY], bind_principal=lambda ctx: principal[0]))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
principal[0] = 12345
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, token)
_assert_frozen_rejection(exc)
assert seen == []
# -- lone surrogates: every encode on the state path is total ----------------------------
async def test_lone_surrogate_arguments_are_digested_not_crashed() -> None:
"""SDK-defined: a lone UTF-16 surrogate in an argument string digests like any other value."""
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY]))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "\ud800-prod"})
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "\udfff-prod"}, token)
second = await _retry(client, "deploy", {"env": "\ud800-prod"}, token)
_assert_frozen_rejection(exc) # different args reject as a binding mismatch, not an internal error
assert isinstance(second, CallToolResult)
assert seen == ["awaiting-confirm"]
async def test_lone_surrogate_handler_state_seals_and_restores() -> None:
"""SDK-defined: handler-minted state containing a lone surrogate round-trips through the seal exactly."""
plaintext = "awaiting-\ud800-confirm"
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY]), state=plaintext)
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
second = await _retry(client, "deploy", {"env": "prod"}, token)
assert isinstance(second, CallToolResult)
assert seen == [plaintext]
async def test_lone_surrogate_principal_binds_and_verifies() -> None:
"""SDK-defined: a principal string containing a lone surrogate binds and verifies like any other."""
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY], bind_principal=lambda ctx: "tenant-\ud800"))
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
second = await _retry(client, "deploy", {"env": "prod"}, token)
assert isinstance(second, CallToolResult)
assert seen == ["awaiting-confirm"]
# -- fractional clocks: the configured ttl is the effective ttl --------------------------
async def test_a_fractional_mint_instant_keeps_the_full_ttl(monkeypatch: pytest.MonkeyPatch) -> None:
"""SDK-defined: a token minted at a fractional instant lives the full configured ttl."""
mcp, seen = _manual_server(RequestStateSecurity(keys=[_KEY], ttl=0.5))
clock = _Clock(_T0 + 0.9)
monkeypatch.setattr(request_state_module, "time", clock)
with anyio.fail_after(5):
async with Client(mcp) as client:
token = await _first_round(client, "deploy", {"env": "prod"})
clock.now = _T0 + 1.3 # 0.4s after mint, inside the 0.5s ttl
second = await _retry(client, "deploy", {"env": "prod"}, token)
clock.now = _T0 + 2.0
late = await _first_round(client, "deploy", {"env": "prod"})
clock.now = _T0 + 2.6 # 0.6s after mint, past the ttl
with pytest.raises(MCPError) as exc:
await _retry(client, "deploy", {"env": "prod"}, late)
_assert_frozen_rejection(exc)
assert isinstance(second, CallToolResult)
assert seen == ["awaiting-confirm"]
async def test_default_principal_distinguishes_two_subjects_of_one_oauth_client() -> None:
"""Spec-mandated (basic/patterns/mrtr server requirement 5, cross-user reuse): with the
default binding, state sealed for one user of an OAuth client is rejected for another
user of the same client and restored only for the original subject."""
boundary = RequestStateBoundary(RequestStateSecurity(keys=[_KEY]), default_audience="svc")
seen: list[str | None] = []
async def mint(ctx: ServerRequestContext[Any, Any]) -> HandlerResult:
return InputRequiredResult(input_requests={"confirm": _ask("PIN?")}, request_state="alice-secret")
async def restore(ctx: ServerRequestContext[Any, Any]) -> HandlerResult:
assert ctx.params is not None
seen.append(ctx.params["requestState"])
return CallToolResult(content=[TextContent(text="done")])
def request(token: str | None = None) -> ServerRequestContext[Any, Any]:
params: dict[str, Any] = {"name": "fetch_pin", "arguments": {}}
if token is not None:
params["requestState"] = token
return ServerRequestContext(
session=cast("Any", None),
lifespan_context={},
protocol_version="2026-07-28",
method="tools/call",
params=params,
)
def as_user(subject: str) -> AuthenticatedUser:
shared_client = "https://agent.example/client.json"
return AuthenticatedUser(
AccessToken(token=f"at-{subject}", client_id=shared_client, scopes=[], subject=subject)
)
reset = auth_context_var.set(as_user("alice"))
try:
sealed = await boundary(request(), mint)
finally:
auth_context_var.reset(reset)
assert isinstance(sealed, InputRequiredResult)
assert sealed.request_state is not None
reset = auth_context_var.set(as_user("bob"))
try:
with pytest.raises(MCPError) as exc:
await boundary(request(sealed.request_state), restore)
finally:
auth_context_var.reset(reset)
_assert_frozen_rejection(exc)
assert seen == []
reset = auth_context_var.set(as_user("alice"))
try:
result = await boundary(request(sealed.request_state), restore)
finally:
auth_context_var.reset(reset)
assert isinstance(result, CallToolResult)
assert seen == ["alice-secret"]
@pytest.mark.parametrize("name", [None, ""], ids=["unnamed", "empty-string"])
def test_a_shared_key_policy_without_a_real_name_must_set_an_audience(name: str | None) -> None:
"""SDK-defined: explicit keys usually mean a fleet, where the audience claim is what
separates services; without a real name every server would stamp the same placeholder."""
with pytest.raises(ValueError) as excinfo:
MCPServer(name, request_state_security=RequestStateSecurity(keys=[_KEY]))
assert str(excinfo.value) == _MISSING_AUDIENCE
# Every neighboring posture constructs: the default needs no name, and a real
# name or an explicit audience satisfies a shared-key policy.
MCPServer(name)
MCPServer(name, request_state_security=RequestStateSecurity(keys=[_KEY], audience="svc"))
MCPServer("named", request_state_security=RequestStateSecurity(keys=[_KEY]))