"""`docs/servers/handling-errors.md`: every claim the page makes, proved against the real SDK.""" import pytest from mcp_types import INVALID_PARAMS, ErrorData, TextContent, TextResourceContents from docs_src.handling_errors import tutorial001, tutorial002, tutorial003 from mcp import Client, MCPError # See test_index.py for why this is a per-module mark and not a conftest hook. pytestmark = [pytest.mark.anyio, pytest.mark.filterwarnings("error::mcp.MCPDeprecationWarning")] async def test_a_plain_exception_becomes_a_tool_error_the_model_reads() -> None: """tutorial001: any non-`MCPError` exception comes back as `is_error=True` with the message in `content`.""" async with Client(tutorial001.mcp) as client: result = await client.call_tool("get_author", {"title": "Nothing"}) assert result.is_error assert result.content == [ TextContent(type="text", text="Error executing tool get_author: No book titled 'Nothing' in the catalog.") ] assert result.structured_content is None async def test_a_title_the_catalog_knows_is_an_ordinary_result() -> None: """tutorial001: the non-raising path is a plain `is_error=False` result.""" async with Client(tutorial001.mcp) as client: result = await client.call_tool("get_author", {"title": "Dune"}) assert not result.is_error assert result.structured_content == {"result": "Frank Herbert"} async def test_a_bad_argument_never_reaches_the_function() -> None: """tutorial001: schema validation rejects the call before `get_author` runs, as the same kind of tool error.""" async with Client(tutorial001.mcp) as client: result = await client.call_tool("get_author", {"title": 42}) assert result.is_error assert isinstance(result.content[0], TextContent) assert "Input should be a valid string" in result.content[0].text async def test_mcp_error_makes_the_call_itself_fail() -> None: """tutorial002: `MCPError` is not caught. It surfaces as a JSON-RPC error, with `code` and `message` intact.""" async with Client(tutorial002.mcp) as client: with pytest.raises(MCPError) as exc_info: await client.call_tool("get_author", {"title": "Nothing"}) assert exc_info.value.code == INVALID_PARAMS assert exc_info.value.message == "No book titled 'Nothing' in the catalog." async def test_mcp_error_only_fires_on_the_raising_path() -> None: """tutorial002: a title the catalog knows still returns a normal result.""" async with Client(tutorial002.mcp) as client: result = await client.call_tool("get_author", {"title": "Dune"}) assert not result.is_error assert result.structured_content == {"result": "Frank Herbert"} async def test_resource_not_found_error_maps_to_invalid_params() -> None: """tutorial003: `ResourceNotFoundError` from a template handler is `-32602` with the URI in `data`.""" async with Client(tutorial003.mcp) as client: with pytest.raises(MCPError) as exc_info: await client.read_resource("books://Nothing") assert exc_info.value.error == ErrorData( code=INVALID_PARAMS, message="No book titled 'Nothing' in the catalog.", data={"uri": "books://Nothing"}, ) async def test_raise_exceptions_does_not_turn_a_tool_error_into_a_traceback() -> None: """The closing `!!! info`: even `raise_exceptions=True` leaves a failing tool as the `is_error=True` result.""" async with Client(tutorial001.mcp, raise_exceptions=True) as client: result = await client.call_tool("get_author", {"title": "Nothing"}) assert result.is_error assert result.content == [ TextContent(type="text", text="Error executing tool get_author: No book titled 'Nothing' in the catalog.") ] async def test_a_title_the_template_knows_reads_normally() -> None: """tutorial003: the non-raising path resolves the template and returns text contents.""" async with Client(tutorial003.mcp) as client: result = await client.read_resource("books://Dune") (contents,) = result.contents assert isinstance(contents, TextResourceContents) assert contents.text == "Dune by Frank Herbert"