"""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 `ClientPeer` can wrap 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 ClientPeer from mcp.shared.transport_context import TransportContext from .conftest import direct_pair, jsonrpc_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_can_send_request_reflects_dispatch_context_closed_state(): """`can_send_request` must track the dctx, not the static transport flag, so it agrees with whether `send_raw_request` would raise.""" captured: list[BaseContext[TransportContext]] = [] async def server_on_request(ctx: DCtx, method: str, params: Mapping[str, Any] | None) -> dict[str, Any]: captured.append(BaseContext(ctx)) return {} async with running_pair(jsonrpc_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.can_send_request is True assert bctx.can_send_request is False @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 ClientPeer 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 ClientPeer(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)