mcp_streamable_proxy/
detector.rs1use reqwest::header::{ACCEPT, CONTENT_TYPE, HeaderMap, HeaderValue};
2use std::time::Duration;
3
4pub async fn is_streamable_http(url: &str) -> bool {
33 let client = match reqwest::Client::builder()
35 .timeout(Duration::from_secs(5))
36 .build()
37 {
38 Ok(c) => c,
39 Err(_) => return false,
40 };
41
42 let mut headers = HeaderMap::new();
44 headers.insert(
45 ACCEPT,
46 HeaderValue::from_static("application/json, text/event-stream"),
47 );
48 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
49
50 use rmcp::model::{
52 ClientCapabilities, ClientRequest, Implementation, InitializeRequestParams,
53 ProtocolVersion, Request, RequestId,
54 };
55
56 let init_request = ClientRequest::InitializeRequest(Request::new(
57 InitializeRequestParams::new(
58 ClientCapabilities::default(),
59 Implementation::new("mcp-proxy-detector", "0.1.0"),
60 )
61 .with_protocol_version(ProtocolVersion::V_2024_11_05),
62 ));
63
64 let body = rmcp::model::ClientJsonRpcMessage::request(init_request, RequestId::Number(1));
66
67 let response = match client.post(url).headers(headers).json(&body).send().await {
69 Ok(r) => r,
70 Err(_) => return false,
71 };
72
73 let status = response.status();
74 let resp_headers = response.headers().clone();
75
76 if resp_headers.contains_key("mcp-session-id") {
78 return true;
79 }
80 if let Some(content_type) = resp_headers.get(CONTENT_TYPE)
82 && let Ok(ct) = content_type.to_str()
83 && ct.contains("text/event-stream")
84 && status.is_success()
85 {
86 return true;
87 }
88 if let Ok(json) = response.json::<serde_json::Value>().await {
90 let is_jsonrpc = json
92 .get("jsonrpc")
93 .and_then(|v| v.as_str())
94 .map(|v| v == "2.0")
95 .unwrap_or(false);
96 if is_jsonrpc {
97 return true;
98 }
99 }
100
101 if status == reqwest::StatusCode::NOT_ACCEPTABLE {
103 return true;
104 }
105
106 false
107}