Skip to content

Commit b18ddc0

Browse files
committed
fix: emit Mcp-Method on reinit initialized POST
1 parent 3befb50 commit b18ddc0

3 files changed

Lines changed: 163 additions & 128 deletions

File tree

crates/rmcp/Cargo.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ name = "test_protocol_version_negotiation"
294294
required-features = ["server", "client"]
295295
path = "tests/test_protocol_version_negotiation.rs"
296296

297+
[[test]]
298+
name = "test_streamable_http_standard_headers"
299+
required-features = ["server", "client", "transport-streamable-http-server", "reqwest"]
300+
path = "tests/test_streamable_http_standard_headers.rs"
301+
297302
[[test]]
298303
name = "test_streamable_http_4xx_error_body"
299304
required-features = ["transport-streamable-http-client", "transport-streamable-http-client-reqwest"]

crates/rmcp/src/transport/streamable_http_client.rs

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,28 @@ fn cache_tools_from_response(
7878
}
7979
}
8080

81+
/// Derives the negotiated protocol version and base request headers from an
82+
/// initialize response: returns `base` with `MCP-Protocol-Version` injected
83+
/// (per MCP 2025-06-18) plus the version used to gate SEP-2243 headers,
84+
/// defaulting to a pre-SEP version when the response can't be parsed.
85+
fn negotiate_version_headers(
86+
init_response: &ServerJsonRpcMessage,
87+
base: HashMap<HeaderName, HeaderValue>,
88+
) -> (ProtocolVersion, HashMap<HeaderName, HeaderValue>) {
89+
let mut version = ProtocolVersion::default();
90+
let mut headers = base;
91+
if let ServerJsonRpcMessage::Response(response) = init_response {
92+
if let ServerResult::InitializeResult(init_result) = &response.result {
93+
version = init_result.protocol_version.clone();
94+
// HeaderName::from_static requires lowercase
95+
if let Ok(hv) = HeaderValue::from_str(init_result.protocol_version.as_str()) {
96+
headers.insert(HeaderName::from_static("mcp-protocol-version"), hv);
97+
}
98+
}
99+
}
100+
(version, headers)
101+
}
102+
81103
#[derive(Debug)]
82104
#[non_exhaustive]
83105
pub struct AuthRequiredError {
@@ -592,31 +614,29 @@ impl<C: StreamableHttpClient> StreamableHttpClientWorker<C> {
592614

593615
let new_session_id: Option<Arc<str>> = new_session_id_str.map(|s| Arc::from(s.as_str()));
594616

595-
// Start from custom_headers, then inject the negotiated MCP-Protocol-Version
596-
// so all subsequent requests carry the right version (MCP 2025-06-18 spec).
597-
let mut new_protocol_headers = custom_headers;
598-
if let ServerJsonRpcMessage::Response(response) = &init_msg {
599-
if let ServerResult::InitializeResult(init_result) = &response.result {
600-
if let Ok(hv) = HeaderValue::from_str(init_result.protocol_version.as_str()) {
601-
new_protocol_headers
602-
.insert(HeaderName::from_static("mcp-protocol-version"), hv);
603-
}
604-
}
605-
}
617+
let (negotiated_version, new_protocol_headers) =
618+
negotiate_version_headers(&init_msg, custom_headers);
606619

607620
let initialized_notification = ClientJsonRpcMessage::notification(
608621
ClientNotification::InitializedNotification(InitializedNotification {
609622
method: Default::default(),
610623
extensions: Default::default(),
611624
}),
612625
);
626+
// SEP-2243: notifications carry no Mcp-Param-*, so an empty tool cache suffices.
627+
let initialized_headers = build_request_headers(
628+
&new_protocol_headers,
629+
&initialized_notification,
630+
&HashMap::new(),
631+
&negotiated_version,
632+
);
613633
client
614634
.post_message(
615635
uri,
616636
initialized_notification,
617637
new_session_id.clone(),
618638
auth_header,
619-
new_protocol_headers.clone(),
639+
initialized_headers,
620640
)
621641
.await?
622642
.expect_accepted_or_json::<C::Error>()?;
@@ -692,25 +712,8 @@ impl<C: StreamableHttpClient> Worker for StreamableHttpClientWorker<C> {
692712
}
693713
None
694714
};
695-
// Extract the negotiated protocol version from the init response
696-
// and build a custom headers map that includes MCP-Protocol-Version
697-
// for all subsequent HTTP requests (per MCP 2025-06-18 spec).
698-
// Negotiated protocol version gates SEP-2243 standard headers; default to a
699-
// pre-SEP version so headers are omitted if the version can't be determined.
700-
let mut negotiated_version = ProtocolVersion::default();
701-
let mut protocol_headers = {
702-
let mut headers = config.custom_headers.clone();
703-
if let ServerJsonRpcMessage::Response(response) = &message {
704-
if let ServerResult::InitializeResult(init_result) = &response.result {
705-
negotiated_version = init_result.protocol_version.clone();
706-
if let Ok(hv) = HeaderValue::from_str(init_result.protocol_version.as_str()) {
707-
// HeaderName::from_static requires lowercase
708-
headers.insert(HeaderName::from_static("mcp-protocol-version"), hv);
709-
}
710-
}
711-
}
712-
headers
713-
};
715+
let (negotiated_version, mut protocol_headers) =
716+
negotiate_version_headers(&message, config.custom_headers.clone());
714717
// SEP-2243: tool input schemas (name -> schema) cached from tools/list responses,
715718
// used to promote annotated tools/call arguments to Mcp-Param-* headers.
716719
let mut tool_header_cache: HashMap<String, Arc<JsonObject>> = HashMap::new();

0 commit comments

Comments
 (0)