use reqwest::header::{ACCEPT, CONTENT_TYPE, HeaderMap, HeaderValue};
use std::collections::HashMap;
use std::time::Duration;
pub async fn is_streamable_http(url: &str) -> bool {
is_streamable_http_with_headers(url, None).await
}
pub async fn is_streamable_http_with_headers(
url: &str,
custom_headers: Option<&HashMap<String, String>>,
) -> bool {
let client = match reqwest::Client::builder()
.timeout(Duration::from_secs(5))
.build()
{
Ok(c) => c,
Err(_) => return false,
};
let mut headers = HeaderMap::new();
headers.insert(
ACCEPT,
HeaderValue::from_static("application/json, text/event-stream"),
);
headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
if let Some(custom) = custom_headers {
for (key, value) in custom {
if let (Ok(name), Ok(val)) = (
reqwest::header::HeaderName::try_from(key.as_str()),
HeaderValue::from_str(value),
) {
headers.insert(name, val);
}
}
}
use rmcp::model::{
ClientCapabilities, ClientRequest, Implementation, InitializeRequestParams,
ProtocolVersion, Request, RequestId,
};
let init_request = ClientRequest::InitializeRequest(Request::new(
InitializeRequestParams::new(
ClientCapabilities::default(),
Implementation::new("mcp-proxy-detector", "0.1.0"),
)
.with_protocol_version(ProtocolVersion::V_2024_11_05),
));
let body = rmcp::model::ClientJsonRpcMessage::request(init_request, RequestId::Number(1));
let response = match client.post(url).headers(headers).json(&body).send().await {
Ok(r) => r,
Err(_) => return false,
};
let status = response.status();
let resp_headers = response.headers().clone();
if resp_headers.contains_key("mcp-session-id") {
return true;
}
if let Some(content_type) = resp_headers.get(CONTENT_TYPE)
&& let Ok(ct) = content_type.to_str()
&& ct.contains("text/event-stream")
&& status.is_success()
{
return true;
}
if let Ok(json) = response.json::<serde_json::Value>().await {
let is_jsonrpc = json
.get("jsonrpc")
.and_then(|v| v.as_str())
.map(|v| v == "2.0")
.unwrap_or(false);
if is_jsonrpc {
return true;
}
}
if status == reqwest::StatusCode::NOT_ACCEPTABLE {
return true;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_is_streamable_http_with_headers_backward_compatible() {
let result = is_streamable_http_with_headers("http://localhost:99999/mcp", None).await;
assert!(!result);
}
#[tokio::test]
async fn test_is_streamable_http_with_headers_no_panic() {
let mut headers = HashMap::new();
headers.insert("Authorization".to_string(), "Bearer test-token".to_string());
let result =
is_streamable_http_with_headers("http://localhost:99999/mcp", Some(&headers)).await;
assert!(!result);
}
}