26
26
from mcp .client .streamable_http import streamablehttp_client
27
27
from mcp .server import Server
28
28
from mcp .server .streamable_http import (
29
+ MCP_PROTOCOL_VERSION_HEADER ,
29
30
MCP_SESSION_ID_HEADER ,
30
31
SESSION_ID_PATTERN ,
31
32
EventCallback ,
@@ -576,11 +577,24 @@ def test_session_termination(basic_server, basic_server_url):
576
577
)
577
578
assert response .status_code == 200
578
579
580
+ # Extract negotiated protocol version from SSE response
581
+ init_data = None
582
+ assert response .headers .get ("Content-Type" ) == "text/event-stream"
583
+ for line in response .text .splitlines ():
584
+ if line .startswith ("data: " ):
585
+ init_data = json .loads (line [6 :])
586
+ break
587
+ assert init_data is not None
588
+ negotiated_version = init_data ["result" ]["protocolVersion" ]
589
+
579
590
# Now terminate the session
580
591
session_id = response .headers .get (MCP_SESSION_ID_HEADER )
581
592
response = requests .delete (
582
593
f"{ basic_server_url } /mcp" ,
583
- headers = {MCP_SESSION_ID_HEADER : session_id },
594
+ headers = {
595
+ MCP_SESSION_ID_HEADER : session_id ,
596
+ MCP_PROTOCOL_VERSION_HEADER : negotiated_version ,
597
+ },
584
598
)
585
599
assert response .status_code == 200
586
600
@@ -611,16 +625,27 @@ def test_response(basic_server, basic_server_url):
611
625
)
612
626
assert response .status_code == 200
613
627
614
- # Now terminate the session
628
+ # Extract negotiated protocol version from SSE response
629
+ init_data = None
630
+ assert response .headers .get ("Content-Type" ) == "text/event-stream"
631
+ for line in response .text .splitlines ():
632
+ if line .startswith ("data: " ):
633
+ init_data = json .loads (line [6 :])
634
+ break
635
+ assert init_data is not None
636
+ negotiated_version = init_data ["result" ]["protocolVersion" ]
637
+
638
+ # Now get the session ID
615
639
session_id = response .headers .get (MCP_SESSION_ID_HEADER )
616
640
617
- # Try to use the terminated session
641
+ # Try to use the session with proper headers
618
642
tools_response = requests .post (
619
643
mcp_url ,
620
644
headers = {
621
645
"Accept" : "application/json, text/event-stream" ,
622
646
"Content-Type" : "application/json" ,
623
647
MCP_SESSION_ID_HEADER : session_id , # Use the session ID we got earlier
648
+ MCP_PROTOCOL_VERSION_HEADER : negotiated_version ,
624
649
},
625
650
json = {"jsonrpc" : "2.0" , "method" : "tools/list" , "id" : "tools-1" },
626
651
stream = True ,
@@ -662,12 +687,23 @@ def test_get_sse_stream(basic_server, basic_server_url):
662
687
session_id = init_response .headers .get (MCP_SESSION_ID_HEADER )
663
688
assert session_id is not None
664
689
690
+ # Extract negotiated protocol version from SSE response
691
+ init_data = None
692
+ assert init_response .headers .get ("Content-Type" ) == "text/event-stream"
693
+ for line in init_response .text .splitlines ():
694
+ if line .startswith ("data: " ):
695
+ init_data = json .loads (line [6 :])
696
+ break
697
+ assert init_data is not None
698
+ negotiated_version = init_data ["result" ]["protocolVersion" ]
699
+
665
700
# Now attempt to establish an SSE stream via GET
666
701
get_response = requests .get (
667
702
mcp_url ,
668
703
headers = {
669
704
"Accept" : "text/event-stream" ,
670
705
MCP_SESSION_ID_HEADER : session_id ,
706
+ MCP_PROTOCOL_VERSION_HEADER : negotiated_version ,
671
707
},
672
708
stream = True ,
673
709
)
@@ -682,6 +718,7 @@ def test_get_sse_stream(basic_server, basic_server_url):
682
718
headers = {
683
719
"Accept" : "text/event-stream" ,
684
720
MCP_SESSION_ID_HEADER : session_id ,
721
+ MCP_PROTOCOL_VERSION_HEADER : negotiated_version ,
685
722
},
686
723
stream = True ,
687
724
)
@@ -710,11 +747,22 @@ def test_get_validation(basic_server, basic_server_url):
710
747
session_id = init_response .headers .get (MCP_SESSION_ID_HEADER )
711
748
assert session_id is not None
712
749
750
+ # Extract negotiated protocol version from SSE response
751
+ init_data = None
752
+ assert init_response .headers .get ("Content-Type" ) == "text/event-stream"
753
+ for line in init_response .text .splitlines ():
754
+ if line .startswith ("data: " ):
755
+ init_data = json .loads (line [6 :])
756
+ break
757
+ assert init_data is not None
758
+ negotiated_version = init_data ["result" ]["protocolVersion" ]
759
+
713
760
# Test without Accept header
714
761
response = requests .get (
715
762
mcp_url ,
716
763
headers = {
717
764
MCP_SESSION_ID_HEADER : session_id ,
765
+ MCP_PROTOCOL_VERSION_HEADER : negotiated_version ,
718
766
},
719
767
stream = True ,
720
768
)
@@ -727,6 +775,7 @@ def test_get_validation(basic_server, basic_server_url):
727
775
headers = {
728
776
"Accept" : "application/json" ,
729
777
MCP_SESSION_ID_HEADER : session_id ,
778
+ MCP_PROTOCOL_VERSION_HEADER : negotiated_version ,
730
779
},
731
780
)
732
781
assert response .status_code == 406
@@ -1038,6 +1087,7 @@ async def test_streamablehttp_client_resumption(event_server):
1038
1087
captured_resumption_token = None
1039
1088
captured_notifications = []
1040
1089
tool_started = False
1090
+ captured_protocol_version = None
1041
1091
1042
1092
async def message_handler (
1043
1093
message : RequestResponder [types .ServerRequest , types .ClientResult ]
@@ -1070,6 +1120,8 @@ async def on_resumption_token_update(token: str) -> None:
1070
1120
assert isinstance (result , InitializeResult )
1071
1121
captured_session_id = get_session_id ()
1072
1122
assert captured_session_id is not None
1123
+ # Capture the negotiated protocol version
1124
+ captured_protocol_version = result .protocolVersion
1073
1125
1074
1126
# Start a long-running tool in a task
1075
1127
async with anyio .create_task_group () as tg :
@@ -1104,10 +1156,12 @@ async def run_tool():
1104
1156
captured_notifications_pre = captured_notifications .copy ()
1105
1157
captured_notifications = []
1106
1158
1107
- # Now resume the session with the same mcp-session-id
1159
+ # Now resume the session with the same mcp-session-id and protocol version
1108
1160
headers = {}
1109
1161
if captured_session_id :
1110
1162
headers [MCP_SESSION_ID_HEADER ] = captured_session_id
1163
+ if captured_protocol_version :
1164
+ headers [MCP_PROTOCOL_VERSION_HEADER ] = captured_protocol_version
1111
1165
1112
1166
async with streamablehttp_client (f"{ server_url } /mcp" , headers = headers ) as (
1113
1167
read_stream ,
@@ -1481,7 +1535,7 @@ async def test_server_validates_protocol_version_header(basic_server, basic_serv
1481
1535
)
1482
1536
assert response .status_code == 400
1483
1537
assert (
1484
- "MCP-Protocol-Version" in response .text
1538
+ MCP_PROTOCOL_VERSION_HEADER in response .text
1485
1539
or "protocol version" in response .text .lower ()
1486
1540
)
1487
1541
@@ -1492,13 +1546,13 @@ async def test_server_validates_protocol_version_header(basic_server, basic_serv
1492
1546
"Accept" : "application/json, text/event-stream" ,
1493
1547
"Content-Type" : "application/json" ,
1494
1548
MCP_SESSION_ID_HEADER : session_id ,
1495
- "MCP-Protocol-Version" : "invalid-version" ,
1549
+ MCP_PROTOCOL_VERSION_HEADER : "invalid-version" ,
1496
1550
},
1497
1551
json = {"jsonrpc" : "2.0" , "method" : "tools/list" , "id" : "test-2" },
1498
1552
)
1499
1553
assert response .status_code == 400
1500
1554
assert (
1501
- "MCP-Protocol-Version" in response .text
1555
+ MCP_PROTOCOL_VERSION_HEADER in response .text
1502
1556
or "protocol version" in response .text .lower ()
1503
1557
)
1504
1558
@@ -1509,13 +1563,13 @@ async def test_server_validates_protocol_version_header(basic_server, basic_serv
1509
1563
"Accept" : "application/json, text/event-stream" ,
1510
1564
"Content-Type" : "application/json" ,
1511
1565
MCP_SESSION_ID_HEADER : session_id ,
1512
- "MCP-Protocol-Version" : "1999-01-01" , # Very old unsupported version
1566
+ MCP_PROTOCOL_VERSION_HEADER : "1999-01-01" , # Very old unsupported version
1513
1567
},
1514
1568
json = {"jsonrpc" : "2.0" , "method" : "tools/list" , "id" : "test-3" },
1515
1569
)
1516
1570
assert response .status_code == 400
1517
1571
assert (
1518
- "MCP-Protocol-Version" in response .text
1572
+ MCP_PROTOCOL_VERSION_HEADER in response .text
1519
1573
or "protocol version" in response .text .lower ()
1520
1574
)
1521
1575
@@ -1536,7 +1590,7 @@ async def test_server_validates_protocol_version_header(basic_server, basic_serv
1536
1590
"Accept" : "application/json, text/event-stream" ,
1537
1591
"Content-Type" : "application/json" ,
1538
1592
MCP_SESSION_ID_HEADER : session_id ,
1539
- "MCP-Protocol-Version" : negotiated_version ,
1593
+ MCP_PROTOCOL_VERSION_HEADER : negotiated_version ,
1540
1594
},
1541
1595
json = {"jsonrpc" : "2.0" , "method" : "tools/list" , "id" : "test-4" },
1542
1596
)
0 commit comments