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
89 lines
4.1 KiB
Python
89 lines
4.1 KiB
Python
"""`docs/handlers/context.md`: every claim the page makes, proved against the real SDK."""
|
|
|
|
import re
|
|
|
|
import pytest
|
|
from inline_snapshot import snapshot
|
|
from mcp_types import TextContent, TextResourceContents, ToolListChangedNotification
|
|
|
|
from docs_src.context import tutorial001, tutorial002, tutorial003
|
|
from mcp import Client
|
|
|
|
# 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_the_context_parameter_is_not_in_the_input_schema() -> None:
|
|
"""tutorial001: the injected `Context` never appears in the schema the model sees."""
|
|
async with Client(tutorial001.mcp) as client:
|
|
(tool,) = (await client.list_tools()).tools
|
|
assert tool.input_schema == snapshot(
|
|
{
|
|
"type": "object",
|
|
"properties": {"query": {"title": "Query", "type": "string"}},
|
|
"required": ["query"],
|
|
"title": "search_booksArguments",
|
|
}
|
|
)
|
|
|
|
|
|
async def test_every_request_gets_its_own_context() -> None:
|
|
"""tutorial001: `ctx.request_id` identifies the request being served, so it changes per call."""
|
|
async with Client(tutorial001.mcp) as client:
|
|
first = await client.call_tool("search_books", {"query": "dune"})
|
|
second = await client.call_tool("search_books", {"query": "dune"})
|
|
assert isinstance(first.content[0], TextContent)
|
|
assert isinstance(second.content[0], TextContent)
|
|
assert re.fullmatch(r"\[request \d+\] Found 3 books matching 'dune'\.", first.content[0].text)
|
|
assert first.content[0].text != second.content[0].text
|
|
|
|
|
|
async def test_a_tool_reads_the_servers_own_resource() -> None:
|
|
"""tutorial002: `ctx.read_resource` resolves the URI through the same registry `resources/read` uses."""
|
|
async with Client(tutorial002.mcp) as client:
|
|
result = await client.call_tool("describe_catalog", {})
|
|
assert not result.is_error
|
|
assert result.content == [
|
|
TextContent(type="text", text="The catalog is organised into: fiction, non-fiction, poetry")
|
|
]
|
|
(contents,) = (await client.read_resource("catalog://genres")).contents
|
|
assert isinstance(contents, TextResourceContents)
|
|
assert contents.text == "fiction, non-fiction, poetry"
|
|
|
|
|
|
async def test_a_context_only_tool_takes_no_arguments() -> None:
|
|
"""tutorial002: a tool whose only parameter is the `Context` has an empty input schema."""
|
|
async with Client(tutorial002.mcp) as client:
|
|
tools = {tool.name: tool for tool in (await client.list_tools()).tools}
|
|
assert tools["describe_catalog"].input_schema == snapshot(
|
|
{"type": "object", "properties": {}, "title": "describe_catalogArguments"}
|
|
)
|
|
|
|
|
|
async def test_register_a_tool_at_runtime_and_notify_the_client() -> None:
|
|
"""tutorial003: `mcp.add_tool` takes effect immediately and `send_tool_list_changed` reaches the client."""
|
|
messages: list[object] = []
|
|
|
|
async def collect(message: object) -> None:
|
|
messages.append(message)
|
|
|
|
async with Client(tutorial003.mcp, mode="legacy", message_handler=collect) as client:
|
|
assert [tool.name for tool in (await client.list_tools()).tools] == ["enable_recommendations"]
|
|
|
|
missing = await client.call_tool("recommend_book", {"genre": "fiction"})
|
|
assert missing.is_error
|
|
assert missing.content == [TextContent(type="text", text="Unknown tool: recommend_book")]
|
|
|
|
enabled = await client.call_tool("enable_recommendations", {})
|
|
assert enabled.content == [TextContent(type="text", text="Recommendations are now available.")]
|
|
|
|
assert [tool.name for tool in (await client.list_tools()).tools] == [
|
|
"enable_recommendations",
|
|
"recommend_book",
|
|
]
|
|
result = await client.call_tool("recommend_book", {"genre": "fiction"})
|
|
assert result.content == [TextContent(type="text", text="In fiction, try 'Dune'.")]
|
|
|
|
(notification,) = messages
|
|
assert isinstance(notification, ToolListChangedNotification)
|