1010import time
1111from collections .abc import Generator
1212from typing import Any
13+ from unittest .mock import MagicMock
1314
1415import anyio
1516import httpx
4142from mcp .server .transport_security import TransportSecuritySettings
4243from mcp .shared .context import RequestContext
4344from mcp .shared .exceptions import McpError
44- from mcp .shared .message import ClientMessageMetadata , SessionMessage
45+ from mcp .shared .message import ClientMessageMetadata , ServerMessageMetadata , SessionMessage
4546from mcp .shared .session import RequestResponder
46- from mcp .types import InitializeResult , TextContent , TextResourceContents , Tool
47+ from mcp .types import (
48+ InitializeResult ,
49+ JSONRPCMessage ,
50+ JSONRPCRequest ,
51+ TextContent ,
52+ TextResourceContents ,
53+ Tool ,
54+ )
4755from tests .test_helpers import wait_for_server
4856
4957# Test constants
@@ -1761,6 +1769,116 @@ async def test_handle_sse_event_skips_empty_data():
17611769 await read_stream .aclose ()
17621770
17631771
1772+ @pytest .mark .anyio
1773+ async def test_priming_event_not_sent_for_old_protocol_version ():
1774+ """Test that _maybe_send_priming_event skips for old protocol versions (backwards compat)."""
1775+ # Create a transport with an event store
1776+ transport = StreamableHTTPServerTransport (
1777+ "/mcp" ,
1778+ event_store = SimpleEventStore (),
1779+ )
1780+
1781+ # Create a mock stream writer
1782+ write_stream , read_stream = anyio .create_memory_object_stream [dict [str , Any ]](1 )
1783+
1784+ try :
1785+ # Call _maybe_send_priming_event with OLD protocol version - should NOT send
1786+ await transport ._maybe_send_priming_event ("test-request-id" , write_stream , "2025-06-18" )
1787+
1788+ # Nothing should have been written to the stream
1789+ assert write_stream .statistics ().current_buffer_used == 0
1790+
1791+ # Now test with NEW protocol version - should send
1792+ await transport ._maybe_send_priming_event ("test-request-id-2" , write_stream , "2025-11-25" )
1793+
1794+ # Should have written a priming event
1795+ assert write_stream .statistics ().current_buffer_used == 1
1796+ finally :
1797+ await write_stream .aclose ()
1798+ await read_stream .aclose ()
1799+
1800+
1801+ @pytest .mark .anyio
1802+ async def test_priming_event_not_sent_without_event_store ():
1803+ """Test that _maybe_send_priming_event returns early when no event_store is configured."""
1804+ # Create a transport WITHOUT an event store
1805+ transport = StreamableHTTPServerTransport ("/mcp" )
1806+
1807+ # Create a mock stream writer
1808+ write_stream , read_stream = anyio .create_memory_object_stream [dict [str , Any ]](1 )
1809+
1810+ try :
1811+ # Call _maybe_send_priming_event - should return early without sending
1812+ await transport ._maybe_send_priming_event ("test-request-id" , write_stream , "2025-11-25" )
1813+
1814+ # Nothing should have been written to the stream
1815+ assert write_stream .statistics ().current_buffer_used == 0
1816+ finally :
1817+ await write_stream .aclose ()
1818+ await read_stream .aclose ()
1819+
1820+
1821+ @pytest .mark .anyio
1822+ async def test_priming_event_includes_retry_interval ():
1823+ """Test that _maybe_send_priming_event includes retry field when retry_interval is set."""
1824+ # Create a transport with an event store AND retry_interval
1825+ transport = StreamableHTTPServerTransport (
1826+ "/mcp" ,
1827+ event_store = SimpleEventStore (),
1828+ retry_interval = 5000 ,
1829+ )
1830+
1831+ # Create a mock stream writer
1832+ write_stream , read_stream = anyio .create_memory_object_stream [dict [str , Any ]](1 )
1833+
1834+ try :
1835+ # Call _maybe_send_priming_event with new protocol version
1836+ await transport ._maybe_send_priming_event ("test-request-id" , write_stream , "2025-11-25" )
1837+
1838+ # Should have written a priming event with retry field
1839+ assert write_stream .statistics ().current_buffer_used == 1
1840+
1841+ # Read the event and verify it has retry field
1842+ event = await read_stream .receive ()
1843+ assert "retry" in event
1844+ assert event ["retry" ] == 5000
1845+ finally :
1846+ await write_stream .aclose ()
1847+ await read_stream .aclose ()
1848+
1849+
1850+ @pytest .mark .anyio
1851+ async def test_close_sse_stream_callback_not_provided_for_old_protocol_version ():
1852+ """Test that close_sse_stream callbacks are NOT provided for old protocol versions."""
1853+ # Create a transport with an event store
1854+ transport = StreamableHTTPServerTransport (
1855+ "/mcp" ,
1856+ event_store = SimpleEventStore (),
1857+ )
1858+
1859+ # Create a mock message and request
1860+ mock_message = JSONRPCMessage (root = JSONRPCRequest (jsonrpc = "2.0" , id = "test-1" , method = "tools/list" ))
1861+ mock_request = MagicMock ()
1862+
1863+ # Call _create_session_message with OLD protocol version
1864+ session_msg = transport ._create_session_message (mock_message , mock_request , "test-request-id" , "2025-06-18" )
1865+
1866+ # Callbacks should NOT be provided for old protocol version
1867+ assert session_msg .metadata is not None
1868+ assert isinstance (session_msg .metadata , ServerMessageMetadata )
1869+ assert session_msg .metadata .close_sse_stream is None
1870+ assert session_msg .metadata .close_standalone_sse_stream is None
1871+
1872+ # Now test with NEW protocol version - should provide callbacks
1873+ session_msg_new = transport ._create_session_message (mock_message , mock_request , "test-request-id-2" , "2025-11-25" )
1874+
1875+ # Callbacks SHOULD be provided for new protocol version
1876+ assert session_msg_new .metadata is not None
1877+ assert isinstance (session_msg_new .metadata , ServerMessageMetadata )
1878+ assert session_msg_new .metadata .close_sse_stream is not None
1879+ assert session_msg_new .metadata .close_standalone_sse_stream is not None
1880+
1881+
17641882@pytest .mark .anyio
17651883async def test_streamablehttp_client_receives_priming_event (
17661884 event_server : tuple [SimpleEventStore , str ],
@@ -2060,7 +2178,9 @@ async def on_resumption_token(token: str) -> None:
20602178
20612179
20622180@pytest .mark .anyio
2063- async def test_standalone_get_stream_reconnection (basic_server : None , basic_server_url : str ) -> None :
2181+ async def test_standalone_get_stream_reconnection (
2182+ event_server : tuple [SimpleEventStore , str ],
2183+ ) -> None :
20642184 """
20652185 Test that standalone GET stream automatically reconnects after server closes it.
20662186
@@ -2069,8 +2189,11 @@ async def test_standalone_get_stream_reconnection(basic_server: None, basic_serv
20692189 2. Server closes GET stream
20702190 3. Client reconnects with Last-Event-ID
20712191 4. Client receives notification 2 on new connection
2192+
2193+ Note: Requires event_server fixture (with event store) because close_standalone_sse_stream
2194+ callback is only provided when event_store is configured and protocol version >= 2025-11-25.
20722195 """
2073- server_url = basic_server_url
2196+ _ , server_url = event_server
20742197 received_notifications : list [str ] = []
20752198
20762199 async def message_handler (
0 commit comments