Skip to content

Commit 3948d9b

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 1a9ead0 commit 3948d9b

File tree

1 file changed

+115
-0
lines changed

1 file changed

+115
-0
lines changed

tests/shared/test_streamable_http.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1425,3 +1425,118 @@ 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+
def test_server_validates_protocol_version_header(basic_server, basic_server_url):
1458+
"""Test that server returns 400 Bad Request version header is missing or invalid."""
1459+
# First initialize a session to get a valid session ID
1460+
init_response = requests.post(
1461+
f"{basic_server_url}/mcp",
1462+
headers={
1463+
"Accept": "application/json, text/event-stream",
1464+
"Content-Type": "application/json",
1465+
},
1466+
json=INIT_REQUEST,
1467+
)
1468+
assert init_response.status_code == 200
1469+
session_id = init_response.headers.get(MCP_SESSION_ID_HEADER)
1470+
1471+
# Test request without MCP-Protocol-Version header (should fail)
1472+
response = requests.post(
1473+
f"{basic_server_url}/mcp",
1474+
headers={
1475+
"Accept": "application/json, text/event-stream",
1476+
"Content-Type": "application/json",
1477+
MCP_SESSION_ID_HEADER: session_id,
1478+
},
1479+
json={"jsonrpc": "2.0", "method": "tools/list", "id": "test-1"},
1480+
)
1481+
assert response.status_code == 400
1482+
assert (
1483+
"MCP-Protocol-Version" in response.text
1484+
or "protocol version" in response.text.lower()
1485+
)
1486+
1487+
# Test request with invalid protocol version (should fail)
1488+
response = requests.post(
1489+
f"{basic_server_url}/mcp",
1490+
headers={
1491+
"Accept": "application/json, text/event-stream",
1492+
"Content-Type": "application/json",
1493+
MCP_SESSION_ID_HEADER: session_id,
1494+
"MCP-Protocol-Version": "invalid-version",
1495+
},
1496+
json={"jsonrpc": "2.0", "method": "tools/list", "id": "test-2"},
1497+
)
1498+
assert response.status_code == 400
1499+
assert (
1500+
"MCP-Protocol-Version" in response.text
1501+
or "protocol version" in response.text.lower()
1502+
)
1503+
1504+
# Test request with unsupported protocol version (should fail)
1505+
response = requests.post(
1506+
f"{basic_server_url}/mcp",
1507+
headers={
1508+
"Accept": "application/json, text/event-stream",
1509+
"Content-Type": "application/json",
1510+
MCP_SESSION_ID_HEADER: session_id,
1511+
"MCP-Protocol-Version": "1999-01-01", # Very old unsupported version
1512+
},
1513+
json={"jsonrpc": "2.0", "method": "tools/list", "id": "test-3"},
1514+
)
1515+
assert response.status_code == 400
1516+
assert (
1517+
"MCP-Protocol-Version" in response.text
1518+
or "protocol version" in response.text.lower()
1519+
)
1520+
1521+
# Test request with valid protocol version (should succeed)
1522+
init_data = None
1523+
assert init_response.headers.get("Content-Type") == "text/event-stream"
1524+
for line in init_response.text.splitlines():
1525+
if line.startswith("data: "):
1526+
init_data = json.loads(line[6:])
1527+
break
1528+
1529+
assert init_data is not None
1530+
negotiated_version = init_data["result"]["protocolVersion"]
1531+
1532+
response = requests.post(
1533+
f"{basic_server_url}/mcp",
1534+
headers={
1535+
"Accept": "application/json, text/event-stream",
1536+
"Content-Type": "application/json",
1537+
MCP_SESSION_ID_HEADER: session_id,
1538+
"MCP-Protocol-Version": negotiated_version,
1539+
},
1540+
json={"jsonrpc": "2.0", "method": "tools/list", "id": "test-4"},
1541+
)
1542+
assert response.status_code == 200

0 commit comments

Comments
 (0)