Files
modelcontextprotocol--pytho…/tests/shared/test_context.py
Max Isbey 6f80f8d150 test: move asserts inside async-with for 3.11 coverage instrumentation
coverage.py on Python 3.11 doesn't record statements after an
'async with running_pair(...)' exit when there's a nested
'with anyio.fail_after()' inside. Same workaround as 0a8f0f4 in PR2 —
move the asserts inside the async-with block.
2026-04-20 16:58:42 +00:00

116 lines
4.5 KiB
Python

"""Tests for `BaseContext`.
`BaseContext` is composition over a `DispatchContext` — it forwards
``transport``/``cancel_requested``/``send_raw_request``/``notify``/``progress``
and adds ``meta``. It must satisfy `Outbound` so `PeerMixin` works on it.
"""
from collections.abc import Mapping
from typing import Any
import anyio
import pytest
from mcp.shared.context import BaseContext
from mcp.shared.dispatcher import DispatchContext
from mcp.shared.peer import Peer
from mcp.shared.transport_context import TransportContext
from .conftest import direct_pair
from .test_dispatcher import Recorder, echo_handlers, running_pair
DCtx = DispatchContext[TransportContext]
@pytest.mark.anyio
async def test_base_context_forwards_transport_and_cancel_requested():
captured: list[BaseContext[TransportContext]] = []
async def server_on_request(ctx: DCtx, method: str, params: Mapping[str, Any] | None) -> dict[str, Any]:
bctx = BaseContext(ctx)
captured.append(bctx)
return {}
async with running_pair(direct_pair, server_on_request=server_on_request) as (client, *_):
with anyio.fail_after(5):
await client.send_raw_request("t", None)
bctx = captured[0]
assert bctx.transport.kind == "direct"
assert isinstance(bctx.cancel_requested, anyio.Event)
assert bctx.can_send_request is True
assert bctx.meta is None
@pytest.mark.anyio
async def test_base_context_send_raw_request_and_notify_forward_to_dispatch_context():
crec = Recorder()
c_req, c_notify = echo_handlers(crec)
async def server_on_request(ctx: DCtx, method: str, params: Mapping[str, Any] | None) -> dict[str, Any]:
bctx = BaseContext(ctx)
sample = await bctx.send_raw_request("sampling/createMessage", {"x": 1})
await bctx.notify("notifications/message", {"level": "info"})
return {"sample": sample}
async with running_pair(
direct_pair,
server_on_request=server_on_request,
client_on_request=c_req,
client_on_notify=c_notify,
) as (client, *_):
with anyio.fail_after(5):
result = await client.send_raw_request("tools/call", None)
await crec.notified.wait()
assert crec.requests == [("sampling/createMessage", {"x": 1})]
assert crec.notifications == [("notifications/message", {"level": "info"})]
assert result["sample"] == {"echoed": "sampling/createMessage", "params": {"x": 1}}
@pytest.mark.anyio
async def test_base_context_report_progress_invokes_caller_on_progress():
received: list[tuple[float, float | None, str | None]] = []
async def on_progress(progress: float, total: float | None, message: str | None) -> None:
received.append((progress, total, message))
async def server_on_request(ctx: DCtx, method: str, params: Mapping[str, Any] | None) -> dict[str, Any]:
bctx = BaseContext(ctx)
await bctx.report_progress(0.5, total=1.0, message="halfway")
return {}
async with running_pair(direct_pair, server_on_request=server_on_request) as (client, *_):
with anyio.fail_after(5):
await client.send_raw_request("t", None, {"on_progress": on_progress})
assert received == [(0.5, 1.0, "halfway")]
@pytest.mark.anyio
async def test_base_context_satisfies_outbound_so_peer_mixin_works():
"""Wrapping a BaseContext in Peer proves it satisfies Outbound structurally."""
async def server_on_request(ctx: DCtx, method: str, params: Mapping[str, Any] | None) -> dict[str, Any]:
bctx = BaseContext(ctx)
await Peer(bctx).ping()
return {}
crec = Recorder()
c_req, c_notify = echo_handlers(crec)
async with running_pair(
direct_pair, server_on_request=server_on_request, client_on_request=c_req, client_on_notify=c_notify
) as (client, *_):
with anyio.fail_after(5):
await client.send_raw_request("t", None)
assert crec.requests == [("ping", None)]
@pytest.mark.anyio
async def test_base_context_meta_holds_supplied_request_params_meta():
async def server_on_request(ctx: DCtx, method: str, params: Mapping[str, Any] | None) -> dict[str, Any]:
bctx = BaseContext(ctx, meta={"progressToken": "abc"})
assert bctx.meta is not None and bctx.meta.get("progressToken") == "abc"
return {}
async with running_pair(direct_pair, server_on_request=server_on_request) as (client, *_):
with anyio.fail_after(5):
await client.send_raw_request("t", None)