-
Notifications
You must be signed in to change notification settings - Fork 1.7k
feat: implement MCP-Protocol-Version header requirement for HTTP transport #898
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
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
The client now tracks the negotiated protocol version from the server's response headers, enabling version-aware communication between client and server.
- Add MCP_PROTOCOL_VERSION_HEADER constant - Add _validate_protocol_version method to check header presence and validity - Validate protocol version for all non-initialization requests (POST, GET, DELETE) - Return 400 Bad Request for missing or invalid protocol versions - Update tests to include MCP-Protocol-Version header in requests - Fix test_streamablehttp_client_resumption to pass protocol version when resuming This implements the server-side validation required by the spec change that mandates clients include the negotiated protocol version in all subsequent HTTP requests after initialization. Github-Issue: #548
- Add extract_protocol_version_from_sse helper function to reduce code duplication - Replace repeated protocol version extraction logic in 5 test functions - Fix line length issues in docstrings to comply with 88 char limit This improves test maintainability by centralizing the SSE response parsing logic.
- Add _validate_request_headers method that combines session and protocol validation - Replace repeated calls to _validate_session and _validate_protocol_version - Improves code maintainability and extensibility for future header validations - No functional changes, all tests passing This refactoring makes it easier to add new header validations in the future by having a single entry point for all non-initialization request validations.
This better reflects that the method prepares headers for outgoing HTTP requests, not just updating them with context. The method adds both session ID and protocol version headers as needed.
bdea62c
to
16c94ae
Compare
def _maybe_extract_protocol_version_from_message( | ||
self, | ||
message: JSONRPCMessage, | ||
) -> None: | ||
"""Extract protocol version from initialization response message.""" | ||
if isinstance(message.root, JSONRPCResponse) and message.root.result: | ||
# Check if result has protocolVersion field | ||
result = message.root.result | ||
if "protocolVersion" in result: | ||
self.protocol_version = result["protocolVersion"] | ||
logger.info(f"Negotiated protocol version: {self.protocol_version}") | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
inspecting the response in this way feels a bit wrong on the transport, but it looks like we're "peeking" into the response in various parts of the transport already
it felt a lot cleaner than doing something like creating a callback, passing that back to the session and have the session jam the version back into the transport, but keen for thoughts here
assert resource_update_found, ( | ||
"ResourceUpdatedNotification not received via GET stream" | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ruff
@@ -40,6 +40,7 @@ | |||
GetSessionIdCallback = Callable[[], str | None] | |||
|
|||
MCP_SESSION_ID = "mcp-session-id" | |||
MCP_PROTOCOL_VERSION = "MCP-Protocol-Version" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
capitalization on these headers is inconsistent across the codebase, should we align on kebab-case-everywhere
for headers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we should.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MCP_PROTOCOL_VERSION = "MCP-Protocol-Version" | |
MCP_PROTOCOL_VERSION = "mcp-protocol-version" |
Implement MCP-Protocol-Version header requirement for HTTP transport.
Motivation and Context
This PR implements the MCP-Protocol-Version header requirement as specified in modelcontextprotocol/modelcontextprotocol#548. The implementation ensures that clients extract and store the negotiated protocol version from the initialization response, include the
MCP-Protocol-Version
header in all subsequent HTTP requests after initialization, and servers validate the presence and correctness of this header for non-initialization requests, returning 400 Bad Request for missing or invalid protocol version headers.How Has This Been Tested?
Updated all existing tests to include the MCP-Protocol-Version header, added helper function to extract protocol version from SSE responses, and added specific tests for protocol version validation. All tests pass successfully.
Breaking Changes
This is a breaking change that requires both clients and servers to be updated. Clients that don't send the MCP-Protocol-Version header will receive 400 Bad Request errors from updated servers.
Types of changes
Checklist
Additional context
The implementation follows a straightforward approach:
Client-side (StreamableHTTP): Extract the
protocolVersion
from the initialization response's result field, store it in the transport instance, and include it asMCP-Protocol-Version
header in all subsequent requests via_prepare_request_headers()
.Server-side (StreamableHTTP): Add validation in
_validate_protocol_version()
that checks header presence and protocol version validity (returns 400 if missing or unsupported), apply validation to all non-initialization requests in POST, GET, and DELETE handlers, and consolidate header validation logic in_validate_request_headers()
for extensibility.This implementation follows the stricter requirements from PR #548, not the backwards-compatible version proposed in PR #668.