Files
modelcontextprotocol--pytho…/tests/docs_src/test_resources.py
T
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:10:27 +08:00

120 lines
5.1 KiB
Python

"""`docs/servers/resources.md`: every claim the page makes, proved against the real SDK."""
import base64
import pytest
from inline_snapshot import snapshot
from mcp_types import BlobResourceContents, Resource, ResourceTemplate, TextResourceContents
from docs_src.resources import tutorial001, tutorial002, tutorial003
from mcp import Client
from mcp.server import MCPServer
# 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_function_becomes_a_listed_resource() -> None:
"""tutorial001: the URI, the function name and the docstring are the whole listing entry."""
async with Client(tutorial001.mcp) as client:
(resource,) = (await client.list_resources()).resources
assert resource == snapshot(
Resource(
name="get_config",
uri="config://app",
description="The active shop configuration.",
mime_type="text/plain",
)
)
async def test_read_returns_the_return_value_as_text() -> None:
"""tutorial001: reading the URI runs the function and wraps the `str` in `TextResourceContents`."""
async with Client(tutorial001.mcp) as client:
result = await client.read_resource("config://app")
assert result.contents == [
TextResourceContents(uri="config://app", mime_type="text/plain", text="theme=dark\nlanguage=en")
]
async def test_template_is_listed_separately_from_resources() -> None:
"""tutorial002: a `{placeholder}` moves the entry from `resources/list` to `resources/templates/list`."""
async with Client(tutorial002.mcp) as client:
assert [r.uri for r in (await client.list_resources()).resources] == ["config://app"]
(template,) = (await client.list_resource_templates()).resource_templates
assert template == snapshot(
ResourceTemplate(
name="get_user_profile",
uri_template="users://{user_id}/profile",
description="A customer's profile.",
mime_type="text/plain",
)
)
async def test_reading_a_template_fills_the_placeholder() -> None:
"""tutorial002: the client reads a concrete URI; the matched value arrives as the function argument."""
async with Client(tutorial002.mcp) as client:
result = await client.read_resource("users://42/profile")
assert result.contents == [
TextResourceContents(
uri="users://42/profile", mime_type="text/plain", text="User 42: 12 orders since 2021."
)
]
def test_uri_params_must_match_function_params() -> None:
"""The `!!! check`: a placeholder/parameter mismatch is rejected at decoration time, not at read time."""
broken = MCPServer("Bookshop")
with pytest.raises(ValueError) as exc_info:
@broken.resource("users://{user_id}/profile")
def get_user_profile(user: str) -> None:
"""A customer's profile."""
assert str(exc_info.value) == snapshot(
"Mismatch between URI parameters {'user_id'} and function parameters {'user'}"
)
async def test_mime_type_is_what_you_declare() -> None:
"""tutorial003: `mime_type=` lands in the listing verbatim; the SDK never guesses it from the value."""
async with Client(tutorial003.mcp) as client:
resources = (await client.list_resources()).resources
assert {r.uri: r.mime_type for r in resources} == snapshot(
{
"docs://readme": "text/markdown",
"stats://catalog": "application/json",
"covers://placeholder": "image/gif",
}
)
async def test_str_return_is_sent_as_is() -> None:
"""tutorial003: a `str` return value is the text content, untouched."""
async with Client(tutorial003.mcp) as client:
(content,) = (await client.read_resource("docs://readme")).contents
assert isinstance(content, TextResourceContents)
assert content.text == "# Bookshop\n\nSearch the catalog with the `search_books` tool."
async def test_dict_return_becomes_json_text() -> None:
"""tutorial003: a non-`str`, non-`bytes` return value is serialised to JSON text."""
async with Client(tutorial003.mcp) as client:
(content,) = (await client.read_resource("stats://catalog")).contents
assert isinstance(content, TextResourceContents)
assert content.text == snapshot('{\n "books": 1204,\n "authors": 391\n}')
async def test_bytes_return_becomes_a_blob() -> None:
"""tutorial003: a `bytes` return value arrives as `BlobResourceContents`, base64-encoded in `blob`."""
async with Client(tutorial003.mcp) as client:
(content,) = (await client.read_resource("covers://placeholder")).contents
assert isinstance(content, BlobResourceContents)
assert content == BlobResourceContents(
uri="covers://placeholder",
mime_type="image/gif",
blob="R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
)
assert base64.b64decode(content.blob) == tutorial003.placeholder_cover()