"""`docs/tutorial/dependencies.md`: every claim the page makes, proved against the real SDK.""" from typing import Literal import pytest from inline_snapshot import snapshot from mcp_types import ElicitRequestParams, ElicitResult, TextContent from docs_src.dependencies import tutorial001, tutorial002, tutorial003 from mcp import Client from mcp.client import ClientRequestContext pytestmark = [pytest.mark.anyio, pytest.mark.filterwarnings("error::mcp.MCPDeprecationWarning")] async def test_the_resolver_fills_the_parameter_from_the_tools_own_argument() -> None: async with Client(tutorial001.mcp) as client: in_stock = await client.call_tool("reserve_book", {"title": "Dune"}) sold_out = await client.call_tool("reserve_book", {"title": "Neuromancer"}) assert in_stock.content == [TextContent(type="text", text="Reserved 'Dune' (6 copies left).")] assert sold_out.content == [TextContent(type="text", text="'Neuromancer' is out of stock.")] async def test_the_resolved_parameter_is_invisible_to_the_model() -> None: """The snapshot is the exact input schema shown on the docs page.""" async with Client(tutorial001.mcp) as client: (tool,) = (await client.list_tools()).tools assert tool.input_schema == snapshot( { "type": "object", "properties": {"title": {"title": "Title", "type": "string"}}, "required": ["title"], "title": "reserve_bookArguments", } ) async def test_a_client_supplied_value_for_a_resolved_parameter_is_ignored() -> None: async with Client(tutorial001.mcp) as client: result = await client.call_tool("reserve_book", {"title": "Dune", "stock": {"title": "Dune", "copies": 999}}) assert result.content == [TextContent(type="text", text="Reserved 'Dune' (6 copies left).")] async def test_a_resolver_can_depend_on_another_resolver() -> None: async with Client(tutorial002.mcp) as client: in_stock = await client.call_tool("order_book", {"title": "Dune"}) backorder = await client.call_tool("order_book", {"title": "Neuromancer"}) assert in_stock.content == [TextContent(type="text", text="Ordered 'Dune'; it arrives tomorrow.")] assert backorder.content == [ TextContent(type="text", text="'Neuromancer' is on backorder; it would arrive in 2-3 weeks.") ] async def test_a_shared_dependency_runs_once_per_call(monkeypatch: pytest.MonkeyPatch) -> None: class CountingInventory: def __init__(self, data: dict[str, int]) -> None: self.data = data self.lookups: list[str] = [] def get(self, key: str, default: int) -> int: self.lookups.append(key) return self.data.get(key, default) inventory = CountingInventory(dict(tutorial002.INVENTORY)) monkeypatch.setattr(tutorial002, "INVENTORY", inventory) async with Client(tutorial002.mcp) as client: await client.call_tool("order_book", {"title": "Dune"}) assert inventory.lookups == ["Dune"] # Memoization is per call, not per server: the next call looks the title up again. await client.call_tool("order_book", {"title": "Dune"}) assert inventory.lookups == ["Dune", "Dune"] # The docs' `!!! info` claims tutorial003 is transport-independent, so each claim is proved in both # modes: "legacy" elicits synchronously mid-call (2025-11-25 and earlier); "auto" negotiates # 2026-07-28, where the question rides a multi-round-trip `tools/call` and `Client` drives retries. @pytest.mark.parametrize("mode", ["legacy", "auto"]) async def test_an_in_stock_order_asks_no_question(mode: Literal["legacy", "auto"]) -> None: async def never(context: ClientRequestContext, params: ElicitRequestParams) -> ElicitResult: # pragma: no cover raise AssertionError("an in-stock order must not elicit") async with Client(tutorial003.mcp, mode=mode, elicitation_callback=never) as client: result = await client.call_tool("order_book", {"title": "Dune"}) assert result.content == [TextContent(type="text", text="Ordered 'Dune'.")] @pytest.mark.parametrize("mode", ["legacy", "auto"]) @pytest.mark.parametrize( ("confirm", "expected"), [ (True, "Backordered 'Neuromancer'; it ships in 2-3 weeks."), (False, "No order placed."), ], ) async def test_an_out_of_stock_order_asks_and_honours_the_answer( mode: Literal["legacy", "auto"], confirm: bool, expected: str ) -> None: asked: list[str] = [] async def on_elicit(context: ClientRequestContext, params: ElicitRequestParams) -> ElicitResult: asked.append(params.message) return ElicitResult(action="accept", content={"confirm": confirm}) async with Client(tutorial003.mcp, mode=mode, elicitation_callback=on_elicit) as client: result = await client.call_tool("order_book", {"title": "Neuromancer"}) assert result.content == [TextContent(type="text", text=expected)] assert asked == ["'Neuromancer' is out of stock (2-3 weeks). Order anyway?"] @pytest.mark.parametrize("mode", ["legacy", "auto"]) async def test_declining_an_unwrapped_dependency_aborts_the_call(mode: Literal["legacy", "auto"]) -> None: """The asserted error text is the one shown on the docs page.""" async def decline(context: ClientRequestContext, params: ElicitRequestParams) -> ElicitResult: return ElicitResult(action="decline") async with Client(tutorial003.mcp, mode=mode, elicitation_callback=decline) as client: result = await client.call_tool("order_book", {"title": "Neuromancer"}) assert result.is_error assert isinstance(result.content[0], TextContent) assert result.content[0].text == ( "Error executing tool order_book: Resolver for parameter 'backorder' could not resolve: elicitation was decline" )