1#![cfg_attr(not(all(feature = "stdio", feature = "http")), allow(dead_code))]
22#![allow(deprecated)]
24
25use std::collections::HashMap;
26use std::future::Future;
27use std::pin::Pin;
28use std::sync::{Arc, Mutex, PoisonError};
29use std::time::Duration;
30
31use serde::{Deserialize, Serialize};
32use serde_json::value::RawValue;
33use serde_json::{Map, Value};
34use tokio::sync::{mpsc, oneshot};
35use tokio_util::sync::CancellationToken;
36
37use crate::context::{RequestMeta, TransportContext};
38use crate::error::McpError;
39use crate::jsonrpc::{ClientResponse, Message, Request, Response};
40use crate::mrtr::now_secs;
41use crate::server::{McpRequest, Server};
42use crate::spec::{self, RequestId};
43
44pub const LEGACY_PROTOCOL_VERSIONS: &[&str] = &["2025-11-25", "2025-06-18"];
46
47const SESSION_CONTEXT: &[u8] = b"flymcprust/legacy-session/v1\0";
49
50const MAX_INPUT_ROUNDS: usize = 16;
52
53const MAX_DECLARATION_BYTES: usize = 8 * 1024;
56
57const LEGACY_RESOURCE_NOT_FOUND: i64 = -32002;
59
60#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
62pub struct LegacySession {
63 #[serde(rename = "v")]
65 pub(crate) version: String,
66 #[serde(rename = "c", default)]
68 pub(crate) capabilities: Value,
69 #[serde(rename = "i", default, skip_serializing_if = "Option::is_none")]
71 pub(crate) client_info: Option<Value>,
72 #[serde(rename = "e", default)]
74 pub(crate) expires: u64,
75 #[serde(rename = "n", default)]
78 pub(crate) nonce: String,
79 #[allow(deprecated)]
82 #[serde(rename = "l", default, skip_serializing_if = "Option::is_none")]
83 pub(crate) log_level: Option<spec::LoggingLevel>,
84 #[serde(rename = "p", default, skip_serializing_if = "Option::is_none")]
87 pub(crate) principal: Option<String>,
88}
89
90impl LegacySession {
91 pub(crate) fn sessionless(version: &str) -> Self {
94 Self {
95 version: version.to_owned(),
96 ..Self::default()
97 }
98 }
99
100 #[must_use]
102 pub fn version(&self) -> &str {
103 &self.version
104 }
105
106 pub(crate) fn request_meta(&self) -> RequestMeta {
108 RequestMeta {
109 protocol_version: self.version.clone(),
110 client_capabilities: serde_json::from_value(self.capabilities.clone())
111 .unwrap_or_default(),
112 client_info: self
113 .client_info
114 .clone()
115 .and_then(|info| serde_json::from_value(info).ok()),
116 progress_token: None,
117 #[allow(deprecated)]
118 log_level: Some(self.log_level.unwrap_or(spec::LoggingLevel::Debug)),
119 }
120 }
121}
122
123#[must_use]
125pub fn is_legacy_version(version: &str) -> bool {
126 LEGACY_PROTOCOL_VERSIONS.contains(&version)
127}
128
129pub(crate) trait Peer: Send + Sync {
131 fn request<'a>(
133 &'a self,
134 method: &'a str,
135 params: &'a Value,
136 ) -> Pin<Box<dyn Future<Output = Result<Value, McpError>> + Send + 'a>>;
137}
138
139#[derive(Debug, Default)]
143pub(crate) struct PendingRequests {
144 map: Mutex<HashMap<String, Pending>>,
145}
146
147#[derive(Debug)]
148struct Pending {
149 owner: u64,
150 reply: oneshot::Sender<Result<Value, Value>>,
151}
152
153impl PendingRequests {
154 fn register(&self, owner: u64) -> Option<(String, oneshot::Receiver<Result<Value, Value>>)> {
155 let mut bytes = [0u8; 16];
156 getrandom::fill(&mut bytes).ok()?;
157 let id: String = bytes.iter().map(|b| format!("{b:02x}")).collect();
158 let id = format!("flymcprust-{id}");
159 let (reply, receiver) = oneshot::channel();
160 self.lock().insert(id.clone(), Pending { owner, reply });
161 Some((id, receiver))
162 }
163
164 fn forget(&self, id: &str) {
165 self.lock().remove(id);
166 }
167
168 pub(crate) fn complete(&self, response: ClientResponse, owner: u64) -> bool {
171 let RequestId::String(id) = &response.id else {
172 return false;
173 };
174 let mut map = self.lock();
175 match map.get(id) {
176 Some(pending) if pending.owner == owner => {}
177 _ => return false,
178 }
179 map.remove(id)
180 .is_some_and(|pending| pending.reply.send(response.outcome).is_ok())
181 }
182
183 fn lock(&self) -> std::sync::MutexGuard<'_, HashMap<String, Pending>> {
184 self.map.lock().unwrap_or_else(PoisonError::into_inner)
185 }
186}
187
188#[derive(Debug, Clone)]
191pub(crate) struct ChannelPeer {
192 pub(crate) pending: Arc<PendingRequests>,
193 pub(crate) owner: u64,
194 pub(crate) out: mpsc::Sender<Vec<u8>>,
195 pub(crate) timeout: Duration,
196}
197
198impl Peer for ChannelPeer {
199 fn request<'a>(
200 &'a self,
201 method: &'a str,
202 params: &'a Value,
203 ) -> Pin<Box<dyn Future<Output = Result<Value, McpError>> + Send + 'a>> {
204 Box::pin(async move {
205 let (id, receiver) = self
206 .pending
207 .register(self.owner)
208 .ok_or_else(McpError::internal)?;
209 let message = serde_json::json!({
210 "jsonrpc": "2.0", "id": id, "method": method, "params": params,
211 });
212 let bytes = serde_json::to_vec(&message).map_err(|_| McpError::internal())?;
213 match tokio::time::timeout(self.timeout, self.out.send(bytes)).await {
215 Ok(Ok(())) => {}
216 Ok(Err(_)) | Err(_) => {
217 self.pending.forget(&id);
218 return Err(McpError::internal_with_message(
219 "The connection to the client is closed or not reading.",
220 ));
221 }
222 }
223 match tokio::time::timeout(self.timeout, receiver).await {
224 Ok(Ok(Ok(result))) => Ok(result),
225 Ok(Ok(Err(_error))) => Err(McpError::request_failed(format!(
226 "The client returned an error for `{method}`."
227 ))),
228 Ok(Err(_)) | Err(_) => {
229 self.pending.forget(&id);
230 tracing::warn!(
231 "a client did not answer a `{method}` request in time\n = help: the \
232 wait is bounded by ServerBuilder::tool_timeout(..)"
233 );
234 Err(McpError::internal_with_message(
235 "The client did not answer in time.",
236 ))
237 }
238 }
239 })
240 }
241}
242
243#[derive(Debug)]
245pub(crate) struct LegacyConfig {
246 pub(crate) enabled: bool,
247 pub(crate) session_ttl: Duration,
248 pub(crate) capabilities: Value,
249 pub(crate) server_info: Value,
250 pub(crate) instructions: Option<String>,
251}
252
253impl LegacyConfig {
254 pub(crate) fn new(
255 enabled: bool,
256 session_ttl: Duration,
257 capabilities: &spec::ServerCapabilities,
258 server_info: &spec::Implementation,
259 instructions: Option<&str>,
260 ) -> Self {
261 let mut capabilities = serde_json::to_value(capabilities).unwrap_or_default();
262 if let Value::Object(map) = &mut capabilities {
263 let apps = map
266 .get("extensions")
267 .and_then(|e| e.get(crate::apps::EXTENSION_ID))
268 .cloned();
269 map.remove("extensions");
270 if let Some(settings) = apps {
271 let mut extensions = Map::new();
272 extensions.insert(crate::apps::EXTENSION_ID.to_owned(), settings);
273 map.insert("extensions".to_owned(), Value::Object(extensions));
274 }
275 }
276 Self {
277 enabled,
278 session_ttl,
279 capabilities,
280 server_info: serde_json::to_value(server_info).unwrap_or_default(),
281 instructions: instructions.map(str::to_owned),
282 }
283 }
284}
285
286#[derive(Deserialize)]
287struct InitializeParams {
288 #[serde(rename = "protocolVersion")]
289 protocol_version: String,
290 #[serde(default)]
291 capabilities: Value,
292 #[serde(rename = "clientInfo", default)]
293 client_info: Option<Value>,
294}
295
296fn to_raw(value: &Value) -> Result<Box<RawValue>, McpError> {
297 serde_json::value::to_raw_value(value).map_err(|_| McpError::internal())
298}
299
300impl Server {
301 #[must_use]
303 pub fn legacy_clients_enabled(&self) -> bool {
304 self.legacy().enabled
305 }
306
307 pub(crate) fn legacy_initialize(
310 &self,
311 params: Option<&RawValue>,
312 ) -> Result<(Box<RawValue>, LegacySession), McpError> {
313 let config = self.legacy();
314 let params: InitializeParams = serde_json::from_str(params.map_or("{}", RawValue::get))
315 .map_err(|e| {
316 McpError::invalid_params(format!(
317 "Invalid initialize params: {}",
318 crate::error::sanitize(&e.to_string())
319 ))
320 })?;
321 let declared = serde_json::to_vec(¶ms.capabilities)
322 .map_or(usize::MAX, |c| c.len())
323 .saturating_add(
324 params
325 .client_info
326 .as_ref()
327 .and_then(|i| serde_json::to_vec(i).ok())
328 .map_or(0, |i| i.len()),
329 );
330 if declared > MAX_DECLARATION_BYTES {
331 return Err(McpError::invalid_params(
332 "Client capabilities and info are too large",
333 ));
334 }
335 let version = if is_legacy_version(¶ms.protocol_version) {
336 params.protocol_version
337 } else {
338 LEGACY_PROTOCOL_VERSIONS
340 .first()
341 .copied()
342 .unwrap_or_default()
343 .to_owned()
344 };
345 let mut result = Map::new();
346 result.insert("protocolVersion".to_owned(), Value::from(version.clone()));
347 result.insert("capabilities".to_owned(), config.capabilities.clone());
348 result.insert("serverInfo".to_owned(), config.server_info.clone());
349 if let Some(instructions) = &config.instructions {
350 result.insert("instructions".to_owned(), Value::from(instructions.clone()));
351 }
352 let mut nonce = [0u8; 16];
353 getrandom::fill(&mut nonce).map_err(|_| McpError::internal())?;
354 let session = LegacySession {
355 version,
356 capabilities: params.capabilities,
357 client_info: params.client_info,
358 expires: now_secs().saturating_add(config.session_ttl.as_secs()),
359 nonce: nonce.iter().map(|b| format!("{b:02x}")).collect(),
360 log_level: None,
361 principal: None,
362 };
363 Ok((to_raw(&Value::Object(result))?, session))
364 }
365
366 pub(crate) fn seal_session(&self, session: &LegacySession) -> Option<String> {
368 self.state_codec().seal_for(SESSION_CONTEXT, session)
369 }
370
371 pub(crate) fn open_session(&self, token: &str) -> Option<LegacySession> {
373 self.state_codec()
374 .open_for::<LegacySession>(SESSION_CONTEXT, token)
375 .filter(|s| s.expires >= now_secs() && is_legacy_version(&s.version))
376 }
377
378 pub(crate) async fn handle_legacy(
381 &self,
382 request: Request,
383 session: &LegacySession,
384 mut transport: TransportContext,
385 cancellation: CancellationToken,
386 peer: &dyn Peer,
387 ) -> Option<Response> {
388 let Request { id, method, params } = request;
389 match method.as_str() {
390 "initialize" => {
391 return Some(match self.legacy_initialize(params.as_deref()) {
392 Ok((result, _)) => Response::Result { id, result },
393 Err(error) => Response::error(Some(id), error),
394 });
395 }
396 "logging/setLevel" | "ping" => {
400 return Some(match to_raw(&Value::Object(Map::new())) {
401 Ok(result) => Response::Result { id, result },
402 Err(error) => Response::error(Some(id), error),
403 });
404 }
405 _ => {}
406 }
407 transport.insert(session.clone());
408
409 let mut params = match params
410 .as_deref()
411 .map(|p| serde_json::from_str::<Value>(p.get()))
412 {
413 None => Map::new(),
414 Some(Ok(Value::Object(map))) => map,
415 Some(Ok(Value::Null)) => Map::new(),
416 Some(_) => {
417 return Some(Response::error(
418 Some(id),
419 McpError::invalid_params("params must be an object"),
420 ));
421 }
422 };
423 for _ in 0..MAX_INPUT_ROUNDS {
424 let raw = match to_raw(&Value::Object(params.clone())) {
425 Ok(raw) => raw,
426 Err(error) => return Some(Response::error(Some(id), error)),
427 };
428 let response = self
429 .handle(McpRequest {
430 message: Message::Request(Request {
431 id: id.clone(),
432 method: method.clone(),
433 params: Some(raw),
434 }),
435 transport: transport.clone(),
436 cancellation: cancellation.clone(),
437 })
438 .await?;
439 let (id, result) = match response {
440 Response::Error { id, error } => {
441 return Some(Response::Error {
442 id,
443 error: legacy_error(error),
444 });
445 }
446 Response::Result { id, result } => (id, result),
447 };
448 let Ok(Value::Object(mut result)) = serde_json::from_str::<Value>(result.get()) else {
449 return Some(Response::error(Some(id), McpError::internal()));
450 };
451 if result.get("resultType").and_then(Value::as_str) != Some("input_required") {
452 for key in ["resultType", "ttlMs", "cacheScope"] {
453 result.remove(key);
454 }
455 return Some(match to_raw(&Value::Object(result)) {
456 Ok(result) => Response::Result { id, result },
457 Err(error) => Response::error(Some(id), error),
458 });
459 }
460
461 let mut answers = Map::new();
463 if let Some(Value::Object(requests)) = result.remove("inputRequests") {
464 for (key, request) in requests {
465 let method = request
466 .get("method")
467 .and_then(Value::as_str)
468 .unwrap_or_default();
469 let empty = Value::Object(Map::new());
470 let request_params = request.get("params").unwrap_or(&empty);
471 let answer = tokio::select! {
472 biased;
473 () = cancellation.cancelled() => return None,
474 answer = peer.request(method, request_params) => answer,
475 };
476 match answer {
477 Ok(answer) => {
478 answers.insert(key, answer);
479 }
480 Err(error) => return Some(Response::error(Some(id), error)),
481 }
482 }
483 }
484 params.insert("inputResponses".to_owned(), Value::Object(answers));
485 match result.remove("requestState") {
486 Some(state) => params.insert("requestState".to_owned(), state),
487 None => params.remove("requestState"),
488 };
489 }
490 tracing::warn!(
491 "a legacy request needed more than {MAX_INPUT_ROUNDS} input rounds\n = help: a \
492 handler keeps asking for input; check that each `ctx.elicit` key is answered once"
493 );
494 Some(Response::error(
495 Some(id),
496 McpError::internal_with_message("Too many input rounds."),
497 ))
498 }
499}
500
501fn legacy_error(error: McpError) -> McpError {
503 let resource_not_found = error.code() == spec::error_codes::INVALID_PARAMS_ERROR
504 && error.message().starts_with("Resource not found")
505 && error.data().is_some_and(|d| d.get("uri").is_some());
506 if resource_not_found {
507 error.with_code(LEGACY_RESOURCE_NOT_FOUND)
508 } else {
509 error
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 #[test]
518 fn versions() {
519 assert!(is_legacy_version("2025-11-25"));
520 assert!(is_legacy_version("2025-06-18"));
521 assert!(!is_legacy_version("2025-03-26"));
522 assert!(!is_legacy_version("2026-07-28"));
523 }
524
525 #[tokio::test]
526 async fn a_client_that_stops_reading_does_not_block_the_handler() {
527 let (out, _unread) = mpsc::channel(1);
528 out.send(b"queued".to_vec()).await.unwrap();
529 let peer = ChannelPeer {
530 pending: Arc::default(),
531 owner: 0,
532 out,
533 timeout: Duration::from_millis(50),
534 };
535 let started = std::time::Instant::now();
536 let params = Value::Object(Map::new());
537 assert!(peer.request("elicitation/create", ¶ms).await.is_err());
538 assert!(started.elapsed() < Duration::from_secs(2));
539 assert!(peer.pending.lock().is_empty());
540 }
541
542 #[test]
543 fn legacy_capabilities_keep_only_the_apps_extension() {
544 let mut extensions = std::collections::BTreeMap::new();
545 extensions.insert(crate::apps::EXTENSION_ID.to_owned(), Map::new());
546 extensions.insert("com.example/other".to_owned(), Map::new());
547 let capabilities = spec::ServerCapabilities {
548 extensions: Some(extensions),
549 ..spec::ServerCapabilities::default()
550 };
551 let info = spec::Implementation::new("s".to_owned(), "1".to_owned());
552 let config = LegacyConfig::new(true, Duration::from_secs(60), &capabilities, &info, None);
553 assert_eq!(
554 config.capabilities["extensions"],
555 serde_json::json!({ "io.modelcontextprotocol/ui": {} })
556 );
557 let plain = LegacyConfig::new(
558 true,
559 Duration::from_secs(60),
560 &spec::ServerCapabilities::default(),
561 &info,
562 None,
563 );
564 assert!(plain.capabilities.get("extensions").is_none());
565 }
566
567 #[test]
568 fn resource_not_found_uses_the_legacy_code() {
569 let e = legacy_error(McpError::resource_not_found("x://y"));
570 assert_eq!(e.code(), -32002);
571 assert_eq!(legacy_error(McpError::invalid_params("bad")).code(), -32602);
572 }
573}