Skip to content

Commit 2363398

Browse files
test: add failing tests for MCP-Protocol-Version header requirement
Add integration tests to verify the new spec requirement that HTTP clients must include the negotiated MCP-Protocol-Version header in all requests after initialization. Tests verify: 1. Client includes MCP-Protocol-Version header after initialization 2. Server validates header presence and returns 400 for missing/invalid 3. Server accepts requests with valid negotiated version These tests currently fail as the feature is not yet implemented. Related to spec change: modelcontextprotocol/modelcontextprotocol#548
1 parent 5441767 commit 2363398

File tree

1 file changed

+119
-3
lines changed

1 file changed

+119
-3
lines changed

tests/shared/test_streamable_http.py

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)