Skip to content

Commit 89455a4

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 185fa49 commit 89455a4

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
@@ -1358,3 +1358,118 @@ async def test_streamablehttp_request_context_isolation(context_aware_server: No
13581358
assert ctx["headers"].get("x-request-id") == f"request-{i}"
13591359
assert ctx["headers"].get("x-custom-value") == f"value-{i}"
13601360
assert ctx["headers"].get("authorization") == f"Bearer token-{i}"
1361+
1362+
1363+
@pytest.mark.anyio
1364+
async def test_client_includes_protocol_version_header_after_init(
1365+
context_aware_server, basic_server_url
1366+
):
1367+
"""Test that client includes MCP-Protocol-Version header after initialization."""
1368+
async with streamablehttp_client(f"{basic_server_url}/mcp") as (
1369+
read_stream,
1370+
write_stream,
1371+
_,
1372+
):
1373+
async with ClientSession(read_stream, write_stream) as session:
1374+
# Initialize and get the negotiated version
1375+
init_result = await session.initialize()
1376+
negotiated_version = init_result.protocolVersion
1377+
1378+
# Call a tool that echoes headers to verify the header is present
1379+
tool_result = await session.call_tool("echo_headers", {})
1380+
1381+
assert len(tool_result.content) == 1
1382+
assert isinstance(tool_result.content[0], TextContent)
1383+
headers_data = json.loads(tool_result.content[0].text)
1384+
1385+
# Verify protocol version header is present
1386+
assert "mcp-protocol-version" in headers_data
1387+
assert headers_data["mcp-protocol-version"] == negotiated_version
1388+
1389+
1390+
def test_server_validates_protocol_version_header(basic_server, basic_server_url):
1391+
"""Test that server returns 400 Bad Request version header is missing or invalid."""
1392+
# First initialize a session to get a valid session ID
1393+
init_response = requests.post(
1394+
f"{basic_server_url}/mcp",
1395+
headers={
1396+
"Accept": "application/json, text/event-stream",
1397+
"Content-Type": "application/json",
1398+
},
1399+
json=INIT_REQUEST,
1400+
)
1401+
assert init_response.status_code == 200
1402+
session_id = init_response.headers.get(MCP_SESSION_ID_HEADER)
1403+
1404+
# Test request without MCP-Protocol-Version header (should fail)
1405+
response = requests.post(
1406+
f"{basic_server_url}/mcp",
1407+
headers={
1408+
"Accept": "application/json, text/event-stream",
1409+
"Content-Type": "application/json",
1410+
MCP_SESSION_ID_HEADER: session_id,
1411+
},
1412+
json={"jsonrpc": "2.0", "method": "tools/list", "id": "test-1"},
1413+
)
1414+
assert response.status_code == 400
1415+
assert (
1416+
"MCP-Protocol-Version" in response.text
1417+
or "protocol version" in response.text.lower()
1418+
)
1419+
1420+
# Test request with invalid protocol version (should fail)
1421+
response = requests.post(
1422+
f"{basic_server_url}/mcp",
1423+
headers={
1424+
"Accept": "application/json, text/event-stream",
1425+
"Content-Type": "application/json",
1426+
MCP_SESSION_ID_HEADER: session_id,
1427+
"MCP-Protocol-Version": "invalid-version",
1428+
},
1429+
json={"jsonrpc": "2.0", "method": "tools/list", "id": "test-2"},
1430+
)
1431+
assert response.status_code == 400
1432+
assert (
1433+
"MCP-Protocol-Version" in response.text
1434+
or "protocol version" in response.text.lower()
1435+
)
1436+
1437+
# Test request with unsupported protocol version (should fail)
1438+
response = requests.post(
1439+
f"{basic_server_url}/mcp",
1440+
headers={
1441+
"Accept": "application/json, text/event-stream",
1442+
"Content-Type": "application/json",
1443+
MCP_SESSION_ID_HEADER: session_id,
1444+
"MCP-Protocol-Version": "1999-01-01", # Very old unsupported version
1445+
},
1446+
json={"jsonrpc": "2.0", "method": "tools/list", "id": "test-3"},
1447+
)
1448+
assert response.status_code == 400
1449+
assert (
1450+
"MCP-Protocol-Version" in response.text
1451+
or "protocol version" in response.text.lower()
1452+
)
1453+
1454+
# Test request with valid protocol version (should succeed)
1455+
init_data = None
1456+
assert init_response.headers.get("Content-Type") == "text/event-stream"
1457+
for line in init_response.text.splitlines():
1458+
if line.startswith("data: "):
1459+
init_data = json.loads(line[6:])
1460+
break
1461+
1462+
assert init_data is not None
1463+
negotiated_version = init_data["result"]["protocolVersion"]
1464+
1465+
response = requests.post(
1466+
f"{basic_server_url}/mcp",
1467+
headers={
1468+
"Accept": "application/json, text/event-stream",
1469+
"Content-Type": "application/json",
1470+
MCP_SESSION_ID_HEADER: session_id,
1471+
"MCP-Protocol-Version": negotiated_version,
1472+
},
1473+
json={"jsonrpc": "2.0", "method": "tools/list", "id": "test-4"},
1474+
)
1475+
assert response.status_code == 200

0 commit comments

Comments
 (0)