@@ -914,9 +914,9 @@ async def message_handler(
914914 assert str (notif .root .params .uri ) == "http://test_resource/"
915915 resource_update_found = True
916916
917- assert (
918- resource_update_found
919- ), "ResourceUpdatedNotification not received via GET stream"
917+ assert resource_update_found , (
918+ "ResourceUpdatedNotification not received via GET stream"
919+ )
920920
921921
922922@pytest .mark .anyio
@@ -1425,3 +1425,119 @@ async def test_streamablehttp_request_context_isolation(
14251425 assert ctx ["headers" ].get ("x-request-id" ) == f"request-{ i } "
14261426 assert ctx ["headers" ].get ("x-custom-value" ) == f"value-{ i } "
14271427 assert ctx ["headers" ].get ("authorization" ) == f"Bearer token-{ i } "
1428+
1429+
1430+ @pytest .mark .anyio
1431+ async def test_client_includes_protocol_version_header_after_init (
1432+ context_aware_server , basic_server_url
1433+ ):
1434+ """Test that client includes MCP-Protocol-Version header after initialization."""
1435+ async with streamablehttp_client (f"{ basic_server_url } /mcp" ) as (
1436+ read_stream ,
1437+ write_stream ,
1438+ _ ,
1439+ ):
1440+ async with ClientSession (read_stream , write_stream ) as session :
1441+ # Initialize and get the negotiated version
1442+ init_result = await session .initialize ()
1443+ negotiated_version = init_result .protocolVersion
1444+
1445+ # Call a tool that echoes headers to verify the header is present
1446+ tool_result = await session .call_tool ("echo_headers" , {})
1447+
1448+ assert len (tool_result .content ) == 1
1449+ assert isinstance (tool_result .content [0 ], TextContent )
1450+ headers_data = json .loads (tool_result .content [0 ].text )
1451+
1452+ # Verify protocol version header is present
1453+ assert "mcp-protocol-version" in headers_data
1454+ assert headers_data ["mcp-protocol-version" ] == negotiated_version
1455+
1456+
1457+ @pytest .mark .anyio
1458+ async def test_server_validates_protocol_version_header (basic_server , basic_server_url ):
1459+ """Test that server returns 400 Bad Request version header is missing or invalid."""
1460+ # First initialize a session to get a valid session ID
1461+ init_response = requests .post (
1462+ f"{ basic_server_url } /mcp" ,
1463+ headers = {
1464+ "Accept" : "application/json, text/event-stream" ,
1465+ "Content-Type" : "application/json" ,
1466+ },
1467+ json = INIT_REQUEST ,
1468+ )
1469+ assert init_response .status_code == 200
1470+ session_id = init_response .headers .get (MCP_SESSION_ID_HEADER )
1471+
1472+ # Test request without MCP-Protocol-Version header (should fail)
1473+ response = requests .post (
1474+ f"{ basic_server_url } /mcp" ,
1475+ headers = {
1476+ "Accept" : "application/json, text/event-stream" ,
1477+ "Content-Type" : "application/json" ,
1478+ MCP_SESSION_ID_HEADER : session_id ,
1479+ },
1480+ json = {"jsonrpc" : "2.0" , "method" : "tools/list" , "id" : "test-1" },
1481+ )
1482+ assert response .status_code == 400
1483+ assert (
1484+ "MCP-Protocol-Version" in response .text
1485+ or "protocol version" in response .text .lower ()
1486+ )
1487+
1488+ # Test request with invalid protocol version (should fail)
1489+ response = requests .post (
1490+ f"{ basic_server_url } /mcp" ,
1491+ headers = {
1492+ "Accept" : "application/json, text/event-stream" ,
1493+ "Content-Type" : "application/json" ,
1494+ MCP_SESSION_ID_HEADER : session_id ,
1495+ "MCP-Protocol-Version" : "invalid-version" ,
1496+ },
1497+ json = {"jsonrpc" : "2.0" , "method" : "tools/list" , "id" : "test-2" },
1498+ )
1499+ assert response .status_code == 400
1500+ assert (
1501+ "MCP-Protocol-Version" in response .text
1502+ or "protocol version" in response .text .lower ()
1503+ )
1504+
1505+ # Test request with unsupported protocol version (should fail)
1506+ response = requests .post (
1507+ f"{ basic_server_url } /mcp" ,
1508+ headers = {
1509+ "Accept" : "application/json, text/event-stream" ,
1510+ "Content-Type" : "application/json" ,
1511+ MCP_SESSION_ID_HEADER : session_id ,
1512+ "MCP-Protocol-Version" : "1999-01-01" , # Very old unsupported version
1513+ },
1514+ json = {"jsonrpc" : "2.0" , "method" : "tools/list" , "id" : "test-3" },
1515+ )
1516+ assert response .status_code == 400
1517+ assert (
1518+ "MCP-Protocol-Version" in response .text
1519+ or "protocol version" in response .text .lower ()
1520+ )
1521+
1522+ # Test request with valid protocol version (should succeed)
1523+ init_data = None
1524+ assert init_response .headers .get ("Content-Type" ) == "text/event-stream"
1525+ for line in init_response .text .splitlines ():
1526+ if line .startswith ("data: " ):
1527+ init_data = json .loads (line [6 :])
1528+ break
1529+
1530+ assert init_data is not None
1531+ negotiated_version = init_data ["result" ]["protocolVersion" ]
1532+
1533+ response = requests .post (
1534+ f"{ basic_server_url } /mcp" ,
1535+ headers = {
1536+ "Accept" : "application/json, text/event-stream" ,
1537+ "Content-Type" : "application/json" ,
1538+ MCP_SESSION_ID_HEADER : session_id ,
1539+ "MCP-Protocol-Version" : negotiated_version ,
1540+ },
1541+ json = {"jsonrpc" : "2.0" , "method" : "tools/list" , "id" : "test-4" },
1542+ )
1543+ assert response .status_code == 200
0 commit comments