mcp_streamable_proxy/
detector.rs1use reqwest::header::{ACCEPT, CONTENT_TYPE, HeaderMap, HeaderValue};
7use std::time::Duration;
8
9pub async fn is_streamable_http(url: &str) -> bool {
36 let client = match reqwest::Client::builder()
38 .timeout(Duration::from_secs(5))
39 .build()
40 {
41 Ok(c) => c,
42 Err(_) => return false,
43 };
44
45 let mut headers = HeaderMap::new();
47 headers.insert(
48 ACCEPT,
49 HeaderValue::from_static("application/json, text/event-stream"),
50 );
51 headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
52
53 use rmcp::model::{
55 ClientCapabilities, ClientRequest, Implementation, InitializeRequestParam, ProtocolVersion,
56 Request, RequestId,
57 };
58
59 let init_request = ClientRequest::InitializeRequest(Request::new(InitializeRequestParam {
60 protocol_version: ProtocolVersion::V_2024_11_05,
61 capabilities: ClientCapabilities::default(),
62 client_info: Implementation {
63 name: "mcp-proxy-detector".to_string(),
64 version: "0.1.0".to_string(),
65 title: None,
66 icons: None,
67 website_url: None,
68 },
69 }));
70
71 let body = rmcp::model::ClientJsonRpcMessage::request(init_request, RequestId::Number(1));
73
74 let response = match client.post(url).headers(headers).json(&body).send().await {
76 Ok(r) => r,
77 Err(_) => return false,
78 };
79
80 let status = response.status();
81 let resp_headers = response.headers().clone();
82
83 if resp_headers.contains_key("mcp-session-id") {
85 return true;
86 }
87
88 if let Some(content_type) = resp_headers.get(CONTENT_TYPE) {
90 if let Ok(ct) = content_type.to_str() {
91 if ct.contains("text/event-stream") && status.is_success() {
92 return true;
93 }
94 }
95 }
96
97 if let Ok(json) = response.json::<serde_json::Value>().await {
99 let is_jsonrpc = json
101 .get("jsonrpc")
102 .and_then(|v| v.as_str())
103 .map(|v| v == "2.0")
104 .unwrap_or(false);
105
106 if is_jsonrpc {
107 return true;
108 }
109 }
110
111 if status == reqwest::StatusCode::NOT_ACCEPTABLE {
113 return true;
114 }
115
116 false
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[tokio::test]
124 async fn test_is_streamable_http_invalid_url() {
125 let result = is_streamable_http("not-a-url").await;
127 assert!(!result);
128 }
129
130 #[tokio::test]
131 async fn test_is_streamable_http_nonexistent_server() {
132 let result = is_streamable_http("http://localhost:99999/mcp").await;
134 assert!(!result);
135 }
136
137 }