|
1 | 1 | """`docs/client/transports.md`: every claim the page makes, proved against the real SDK.""" |
2 | 2 |
|
3 | 3 | import inspect |
| 4 | +from typing import Any |
4 | 5 |
|
| 6 | +import httpx2 |
5 | 7 | import pytest |
6 | 8 |
|
7 | | -from docs_src.client_transports import tutorial001, tutorial004 |
| 9 | +from docs_src.client_transports import tutorial001, tutorial002, tutorial003, tutorial004 |
8 | 10 | from mcp import Client |
9 | 11 | from mcp.client.stdio import get_default_environment |
10 | 12 | from mcp.client.streamable_http import streamable_http_client |
| 13 | +from mcp.server import MCPServer |
11 | 14 |
|
12 | 15 | # See test_index.py for why this is a per-module mark and not a conftest hook. |
13 | 16 | pytestmark = [pytest.mark.anyio, pytest.mark.filterwarnings("error::mcp.MCPDeprecationWarning")] |
@@ -46,6 +49,40 @@ async def test_streamable_http_configuration_lives_on_the_httpx_client() -> None |
46 | 49 | ] |
47 | 50 |
|
48 | 51 |
|
| 52 | +async def test_the_timeout_on_the_page_is_the_sdk_clients_and_a_client_without_one_has_five_seconds( |
| 53 | + monkeypatch: pytest.MonkeyPatch, capsys: pytest.CaptureFixture[str] |
| 54 | +) -> None: |
| 55 | + """tutorial002 and tutorial003: the `timeout=` tutorial003 passes is what `Client(url)` builds for itself |
| 56 | + (30 seconds, 300 for reads); an `httpx2.AsyncClient` built without one has httpx2's 5-second default.""" |
| 57 | + async with httpx2.AsyncClient() as bare: |
| 58 | + assert bare.timeout == httpx2.Timeout(5.0) |
| 59 | + |
| 60 | + mcp = MCPServer("Bookshop") |
| 61 | + |
| 62 | + @mcp.tool() |
| 63 | + def search_books(query: str) -> str: |
| 64 | + """Search the catalog.""" |
| 65 | + raise NotImplementedError |
| 66 | + |
| 67 | + app = mcp.streamable_http_app() |
| 68 | + built: list[httpx2.Timeout] = [] |
| 69 | + |
| 70 | + class InProcessClient(httpx2.AsyncClient): |
| 71 | + """Every `httpx2.AsyncClient` the two programs build, routed to the server above.""" |
| 72 | + |
| 73 | + def __init__(self, **kwargs: Any) -> None: |
| 74 | + super().__init__(transport=httpx2.ASGITransport(app=app), **kwargs) |
| 75 | + built.append(self.timeout) |
| 76 | + |
| 77 | + monkeypatch.setattr(httpx2, "AsyncClient", InProcessClient) |
| 78 | + async with mcp.session_manager.run(): |
| 79 | + await tutorial002.main() |
| 80 | + await tutorial003.main() |
| 81 | + |
| 82 | + assert capsys.readouterr().out == "['search_books']\n['search_books']\n" |
| 83 | + assert built == [httpx2.Timeout(30.0, read=300.0), httpx2.Timeout(30.0, read=300.0)] |
| 84 | + |
| 85 | + |
49 | 86 | async def test_stdio_parameters_go_straight_to_client() -> None: |
50 | 87 | """tutorial004: `Client` takes the `StdioServerParameters` directly, and nothing is spawned until you enter it.""" |
51 | 88 | client = Client(tutorial004.server) |
|
0 commit comments