1- """Tests for the WebSocket transport.
1+ """Smoke test for the WebSocket transport.
22
3- The smoke test (``test_ws_client_basic_connection``) runs the full WS stack
4- end-to-end over a real TCP connection and is what provides coverage of
5- ``src/mcp/client/websocket.py``.
6-
7- The remaining tests verify transport-agnostic MCP semantics (error
8- propagation, client-side timeouts) and use the in-memory ``Client`` transport
9- to avoid the cost and flakiness of real network servers.
3+ Runs the full WS stack end-to-end over a real TCP connection to provide
4+ coverage of ``src/mcp/client/websocket.py``. MCP semantics (error
5+ propagation, timeouts, etc.) are transport-agnostic and are covered in
6+ ``tests/client/test_client.py`` and ``tests/issues/test_88_random_error.py``.
107"""
118
129from collections .abc import Generator
13- from urllib .parse import urlparse
1410
15- import anyio
1611import pytest
1712from starlette .applications import Starlette
1813from starlette .routing import WebSocketRoute
1914from starlette .websockets import WebSocket
2015
21- from mcp import Client , MCPError
2216from mcp .client .session import ClientSession
2317from mcp .client .websocket import websocket_client
24- from mcp .server import Server , ServerRequestContext
18+ from mcp .server import Server
2519from mcp .server .websocket import websocket_server
26- from mcp .types import (
27- EmptyResult ,
28- InitializeResult ,
29- ReadResourceRequestParams ,
30- ReadResourceResult ,
31- TextResourceContents ,
32- )
20+ from mcp .types import EmptyResult , InitializeResult
3321from tests .test_helpers import run_uvicorn_in_thread
3422
3523SERVER_NAME = "test_server_for_WS"
3624
37- pytestmark = pytest .mark .anyio
38-
39-
40- # --- WebSocket transport smoke test (real TCP) -------------------------------
41-
4225
4326def make_server_app () -> Starlette :
4427 srv = Server (SERVER_NAME )
@@ -56,6 +39,7 @@ def ws_server_url() -> Generator[str, None, None]:
5639 yield base_url .replace ("http://" , "ws://" ) + "/ws"
5740
5841
42+ @pytest .mark .anyio
5943async def test_ws_client_basic_connection (ws_server_url : str ) -> None :
6044 async with websocket_client (ws_server_url ) as streams :
6145 async with ClientSession (* streams ) as session :
@@ -65,57 +49,3 @@ async def test_ws_client_basic_connection(ws_server_url: str) -> None:
6549
6650 ping_result = await session .send_ping ()
6751 assert isinstance (ping_result , EmptyResult )
68-
69-
70- # --- In-memory tests (transport-agnostic MCP semantics) ----------------------
71-
72-
73- async def handle_read_resource (ctx : ServerRequestContext , params : ReadResourceRequestParams ) -> ReadResourceResult :
74- parsed = urlparse (str (params .uri ))
75- if parsed .scheme == "foobar" :
76- return ReadResourceResult (
77- contents = [TextResourceContents (uri = str (params .uri ), text = f"Read { parsed .netloc } " , mime_type = "text/plain" )]
78- )
79- elif parsed .scheme == "slow" :
80- # Block indefinitely so the client-side fail_after() fires; the pending
81- # server task is cancelled when the Client context manager exits.
82- await anyio .sleep_forever ()
83- raise MCPError (code = 404 , message = "OOPS! no resource with that URI was found" )
84-
85-
86- @pytest .fixture
87- def server () -> Server :
88- return Server (SERVER_NAME , on_read_resource = handle_read_resource )
89-
90-
91- async def test_ws_client_happy_request_and_response (server : Server ) -> None :
92- async with Client (server ) as client :
93- result = await client .read_resource ("foobar://example" )
94- assert isinstance (result , ReadResourceResult )
95- assert isinstance (result .contents , list )
96- assert len (result .contents ) > 0
97- assert isinstance (result .contents [0 ], TextResourceContents )
98- assert result .contents [0 ].text == "Read example"
99-
100-
101- async def test_ws_client_exception_handling (server : Server ) -> None :
102- async with Client (server ) as client :
103- with pytest .raises (MCPError ) as exc_info :
104- await client .read_resource ("unknown://example" )
105- assert exc_info .value .error .code == 404
106-
107-
108- async def test_ws_client_timeout (server : Server ) -> None :
109- async with Client (server ) as client :
110- with pytest .raises (TimeoutError ):
111- with anyio .fail_after (0.1 ):
112- await client .read_resource ("slow://example" )
113-
114- # Session remains usable after a client-side timeout abandons a request.
115- with anyio .fail_after (5 ):
116- result = await client .read_resource ("foobar://example" )
117- assert isinstance (result , ReadResourceResult )
118- assert isinstance (result .contents , list )
119- assert len (result .contents ) > 0
120- assert isinstance (result .contents [0 ], TextResourceContents )
121- assert result .contents [0 ].text == "Read example"
0 commit comments