fix(server): negotiate initialize as legacy lifecycle - #1171
Open
AprilNEA wants to merge 1 commit into
Open
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Adjusts MCP protocol negotiation so an initialize opener always selects the legacy lifecycle (even when the client offers the modern 2026-07-28 revision), negotiating down to a supported legacy revision when possible and rejecting initialize only when the server supports no legacy revisions. This aligns direct transports and streamable HTTP behavior with the intended dual-era semantics.
Changes:
- Update
initializeprotocol negotiation to consider only legacy revisions and return method not found when no legacy revision is supported. - Ensure streamable HTTP request classification treats
initializeas legacy lifecycle regardless of offered version. - Expand/adjust tests to cover modern offers being negotiated down and modern-only servers rejecting
initialize.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/rmcp/tests/test_stateless_protocol_version.rs | Updates/extends streamable HTTP tests to validate modern-offer negotiation-down and modern-only rejection behavior. |
| crates/rmcp/tests/test_protocol_version_negotiation.rs | Extends direct transport tests to verify modern initialize offers negotiate down to the preferred legacy revision. |
| crates/rmcp/src/transport/streamable_http_server/tower.rs | Applies legacy negotiation result to stateless HTTP initialize handling and treats initialize as selecting legacy lifecycle for classification. |
| crates/rmcp/src/service/server.rs | Changes negotiation function to return Option and rejects initialize when no legacy protocol is supported; updates handshake path accordingly. |
| crates/rmcp/src/service.rs | Updates docs to clarify how supported versions bound legacy initialize negotiation vs discover lifecycle selection. |
| crates/rmcp/src/handler/server.rs | Updates default ServerHandler::initialize to reject initialize when no legacy version can be negotiated. |
Suppressed comments (1)
crates/rmcp/tests/test_stateless_protocol_version.rs:209
- Same issue as the JSON variant: this loop only skips exactly
2026-07-28, so it may fail when newer modern revisions are added. Skip>= 2026-07-28to keep the test aligned with legacy-only initialize negotiation.
if version == &ProtocolVersion::V_2026_07_28 {
continue;
}
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
492
to
+496
| tracing::warn!( | ||
| client_requested = %client_requested, | ||
| server_fallback = %server_fallback, | ||
| "client requested unsupported protocol version; falling back to server default" | ||
| "client requested a version unavailable to initialize; falling back to the server's preferred legacy version" | ||
| ); |
Comment on lines
+188
to
+190
| if version == &ProtocolVersion::V_2026_07_28 { | ||
| continue; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Treat an
initializeopener as selecting the legacy lifecycle, including when the client offers2026-07-28. Negotiate only supported legacy revisions and rejectinitializeonly when the server supports no legacy revision.Motivation and Context
The
2026-07-28protocol replaces the initialize handshake with the discover lifecycle. However, a client that still opens withinitializeis explicitly selecting legacy lifecycle semantics.Previously, a dual-era server could echo
2026-07-28frominitialize, even though that revision does not use the initialize lifecycle. Rejecting every such offer would also unnecessarily break clients capable of using an older revision.This change makes a dual-era server negotiate the offer down to its preferred supported legacy revision (
2025-11-25by default). A modern-only server, with no supported legacy revision, returns Method Not Found.The same rule is applied across direct transports, stateless HTTP handling, and stateful Streamable HTTP session classification. Discover/per-request modern requests remain sessionless.
How Has This Been Tested?
The following focused tests pass locally:
cargo test -p rmcp --test test_protocol_version_negotiation --features clientcargo test -p rmcp --test test_stateless_protocol_version --features 'transport-streamable-http-server reqwest'cargo test -p rmcp --test test_client_lifecycle_modes --features client high_level_server_accepts_discover_startup_without_initializecargo test -p rmcp --test test_subscriptions_streamable_http --features 'client transport-streamable-http-client-reqwest transport-streamable-http-server reqwest' modern_http_lifecycle_stays_sessionless_for_older_application_versioncargo clippy -p rmcp --lib --features client -- -D warningscargo fmt --all --checkgit diff --checkThe behavior was also exercised in MCPlex's Streamable HTTP gateway integration test:
initialize(protocolVersion = 2026-07-28)opens a legacy session and negotiates2025-11-25.Breaking Changes
No API changes are required for users.
This corrects protocol negotiation behavior: an
initializerequest can no longer negotiate2026-07-28. Servers supporting legacy revisions negotiate an appropriate legacy revision; modern-only servers reject the obsolete method.Types of changes
Checklist
Additional context
The lifecycle is selected by how the client opens:
initializeselects legacy lifecycle semantics, regardless of the offered revision.server/discoveror a request carrying complete modern_metaselects the modern stateless lifecycle.ServerHandler::supported_protocol_versionscontinues to advertise all supported revisions through discovery, whileinitializenegotiation considers only revisions older than2026-07-28.