Skip to content

Commit cc66e30

Browse files
authored
fix: accept 200 with empty body in response to notifications in addition to 202 (#849)
1 parent 3529c36 commit cc66e30

3 files changed

Lines changed: 79 additions & 0 deletions

File tree

crates/rmcp/src/transport/common/reqwest/streamable_http_client.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,25 @@ impl StreamableHttpClient for reqwest::Client {
178178
.headers()
179179
.get(reqwest::header::CONTENT_TYPE)
180180
.map(|ct| String::from_utf8_lossy(ct.as_bytes()).to_string());
181+
let content_length = response.content_length();
181182
let session_id = response
182183
.headers()
183184
.get(HEADER_SESSION_ID)
184185
.and_then(|v| v.to_str().ok())
185186
.map(|s| s.to_string());
187+
// Spec requires 202 Accepted for these, but some servers return an empty 200.
188+
// Treat empty success responses as equivalent to Accepted.
189+
if status.is_success()
190+
&& content_length == Some(0)
191+
&& matches!(
192+
message,
193+
ClientJsonRpcMessage::Notification(_)
194+
| ClientJsonRpcMessage::Response(_)
195+
| ClientJsonRpcMessage::Error(_)
196+
)
197+
{
198+
return Ok(StreamableHttpPostResponse::Accepted);
199+
}
186200
// Non-success responses may carry valid JSON-RPC error payloads that
187201
// should be surfaced as McpError rather than lost in TransportSend.
188202
if !status.is_success() {

crates/rmcp/src/transport/common/unix_socket.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,12 +257,29 @@ impl StreamableHttpClient for UnixSocketHttpClient {
257257
}
258258

259259
let content_type = response.headers().get(http::header::CONTENT_TYPE).cloned();
260+
let content_length = response
261+
.headers()
262+
.get(http::header::CONTENT_LENGTH)
263+
.and_then(|v| v.to_str().ok())
264+
.and_then(|v| v.parse::<u64>().ok());
260265
let session_id = response
261266
.headers()
262267
.get(HEADER_SESSION_ID)
263268
.and_then(|v| v.to_str().ok())
264269
.map(|s| s.to_string());
265270

271+
if status.is_success()
272+
&& content_length == Some(0)
273+
&& matches!(
274+
message,
275+
ClientJsonRpcMessage::Notification(_)
276+
| ClientJsonRpcMessage::Response(_)
277+
| ClientJsonRpcMessage::Error(_)
278+
)
279+
{
280+
return Ok(StreamableHttpPostResponse::Accepted);
281+
}
282+
266283
match content_type {
267284
Some(ref ct) if ct.as_bytes().starts_with(EVENT_STREAM_MIME_TYPE.as_bytes()) => {
268285
let sse_stream = SseStream::new(response.into_body()).boxed();
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#![cfg(all(
2+
feature = "transport-streamable-http-client",
3+
feature = "transport-streamable-http-client-reqwest",
4+
not(feature = "local")
5+
))]
6+
7+
use std::{collections::HashMap, sync::Arc};
8+
9+
use rmcp::{
10+
model::{ClientJsonRpcMessage, ClientNotification, InitializedNotification},
11+
transport::streamable_http_client::{StreamableHttpClient, StreamableHttpPostResponse},
12+
};
13+
14+
async fn spawn_empty_ok_server() -> String {
15+
use axum::{Router, http::StatusCode, routing::post};
16+
17+
let router = Router::new().route("/mcp", post(|| async { StatusCode::OK }));
18+
19+
let listener = tokio::net::TcpListener::bind("127.0.0.1:0").await.unwrap();
20+
let addr = listener.local_addr().unwrap();
21+
tokio::spawn(async move {
22+
axum::serve(listener, router).await.unwrap();
23+
});
24+
25+
format!("http://{addr}/mcp")
26+
}
27+
28+
#[tokio::test]
29+
async fn empty_success_response_to_notification_is_accepted() {
30+
let url = spawn_empty_ok_server().await;
31+
let client = reqwest::Client::new();
32+
let result = client
33+
.post_message(
34+
Arc::from(url.as_str()),
35+
ClientJsonRpcMessage::notification(ClientNotification::InitializedNotification(
36+
InitializedNotification::default(),
37+
)),
38+
None,
39+
None,
40+
HashMap::new(),
41+
)
42+
.await;
43+
44+
match result {
45+
Ok(StreamableHttpPostResponse::Accepted) => {}
46+
other => panic!("expected Accepted, got: {other:?}"),
47+
}
48+
}

0 commit comments

Comments
 (0)