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) Waiting to run
87 lines
4.2 KiB
Python
87 lines
4.2 KiB
Python
"""`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"
|