1use std::{
2 future::Future,
3 marker::PhantomData,
4 sync::{Arc, Mutex},
5};
6
7use futures::{SinkExt, StreamExt};
8use rmcp::{
9 service::{RoleServer, RxJsonRpcMessage, ServiceRole, TxJsonRpcMessage},
10 transport::Transport,
11};
12use serde::{Serialize, de::DeserializeOwned};
13use thiserror::Error;
14use tokio::{
15 io::{AsyncRead, AsyncWrite},
16 sync::Mutex as AsyncMutex,
17};
18use tokio_util::{
19 bytes::{Buf, BufMut, BytesMut},
20 codec::{Decoder, Encoder, FramedRead, FramedWrite},
21};
22
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24enum WireProtocol {
25 JsonLine,
26 ContentLength,
27}
28
29#[derive(Debug, Clone)]
30struct SharedProtocol(Arc<Mutex<Option<WireProtocol>>>);
31
32impl SharedProtocol {
33 fn new() -> Self {
34 Self(Arc::new(Mutex::new(None)))
35 }
36
37 fn get(&self) -> Option<WireProtocol> {
38 *self
39 .0
40 .lock()
41 .unwrap_or_else(std::sync::PoisonError::into_inner)
42 }
43
44 fn set_if_unset(&self, protocol: WireProtocol) {
45 let mut guard = self
46 .0
47 .lock()
48 .unwrap_or_else(std::sync::PoisonError::into_inner);
49 if guard.is_none() {
50 *guard = Some(protocol);
51 }
52 }
53}
54
55pub type TransportWriter<Role, W> =
56 FramedWrite<W, HybridJsonRpcMessageCodec<TxJsonRpcMessage<Role>>>;
57
58pub struct HybridStdioTransport<Role: ServiceRole, R: AsyncRead, W: AsyncWrite> {
59 read: FramedRead<R, HybridJsonRpcMessageCodec<RxJsonRpcMessage<Role>>>,
60 write: Arc<AsyncMutex<Option<TransportWriter<Role, W>>>>,
61}
62
63impl<Role: ServiceRole, R, W> HybridStdioTransport<Role, R, W>
64where
65 R: Send + AsyncRead + Unpin,
66 W: Send + AsyncWrite + Unpin + 'static,
67{
68 pub fn new(read: R, write: W) -> Self {
69 let protocol = SharedProtocol::new();
70 let read = FramedRead::new(
71 read,
72 HybridJsonRpcMessageCodec::<RxJsonRpcMessage<Role>>::new(protocol.clone()),
73 );
74 let write = Arc::new(AsyncMutex::new(Some(FramedWrite::new(
75 write,
76 HybridJsonRpcMessageCodec::<TxJsonRpcMessage<Role>>::new(protocol),
77 ))));
78 Self { read, write }
79 }
80}
81
82impl<R, W> HybridStdioTransport<RoleServer, R, W>
83where
84 R: Send + AsyncRead + Unpin,
85 W: Send + AsyncWrite + Unpin + 'static,
86{
87 pub fn new_server(read: R, write: W) -> Self {
88 Self::new(read, write)
89 }
90}
91
92impl<Role: ServiceRole, R, W> Transport<Role> for HybridStdioTransport<Role, R, W>
93where
94 R: Send + AsyncRead + Unpin,
95 W: Send + AsyncWrite + Unpin + 'static,
96{
97 type Error = std::io::Error;
98
99 fn send(
100 &mut self,
101 item: TxJsonRpcMessage<Role>,
102 ) -> impl Future<Output = Result<(), Self::Error>> + Send + 'static {
103 let lock = self.write.clone();
104 async move {
105 let mut write = lock.lock().await;
106 if let Some(ref mut write) = *write {
107 write.send(item).await.map_err(Into::into)
108 } else {
109 Err(std::io::Error::new(
110 std::io::ErrorKind::NotConnected,
111 "Transport is closed",
112 ))
113 }
114 }
115 }
116
117 fn receive(&mut self) -> impl Future<Output = Option<RxJsonRpcMessage<Role>>> + Send {
118 let next = self.read.next();
119 async {
120 next.await.and_then(|result| {
121 result
122 .inspect_err(|error| {
123 tracing::error!("Error reading from stream: {}", error);
124 })
125 .ok()
126 })
127 }
128 }
129
130 async fn close(&mut self) -> Result<(), Self::Error> {
131 let mut write = self.write.lock().await;
132 drop(write.take());
133 Ok(())
134 }
135}
136
137#[derive(Debug, Clone)]
138pub struct HybridJsonRpcMessageCodec<T> {
139 _marker: PhantomData<fn() -> T>,
140 next_index: usize,
141 max_length: usize,
142 is_discarding: bool,
143 protocol: SharedProtocol,
144}
145
146impl<T> HybridJsonRpcMessageCodec<T> {
147 fn new(protocol: SharedProtocol) -> Self {
148 Self {
149 _marker: PhantomData,
150 next_index: 0,
151 max_length: 32 * 1024 * 1024, is_discarding: false,
153 protocol,
154 }
155 }
156}
157
158fn without_carriage_return(s: &[u8]) -> &[u8] {
159 if let Some(&b'\r') = s.last() {
160 &s[..s.len() - 1]
161 } else {
162 s
163 }
164}
165
166fn is_standard_method(method: &str) -> bool {
167 matches!(
168 method,
169 "initialize"
170 | "ping"
171 | "prompts/get"
172 | "prompts/list"
173 | "resources/list"
174 | "resources/read"
175 | "resources/subscribe"
176 | "resources/unsubscribe"
177 | "resources/templates/list"
178 | "tools/call"
179 | "tools/list"
180 | "completion/complete"
181 | "logging/setLevel"
182 | "roots/list"
183 | "sampling/createMessage"
184 ) || is_standard_notification(method)
185}
186
187fn is_standard_notification(method: &str) -> bool {
188 matches!(
189 method,
190 "notifications/cancelled"
191 | "notifications/initialized"
192 | "notifications/message"
193 | "notifications/progress"
194 | "notifications/prompts/list_changed"
195 | "notifications/resources/list_changed"
196 | "notifications/resources/updated"
197 | "notifications/roots/list_changed"
198 | "notifications/tools/list_changed"
199 )
200}
201
202fn should_ignore_notification(json_value: &serde_json::Value, method: &str) -> bool {
203 let is_notification = json_value.get("id").is_none();
204 if is_notification && !is_standard_method(method) {
205 tracing::trace!(
206 "Ignoring non-MCP notification '{}' for compatibility",
207 method
208 );
209 return true;
210 }
211
212 matches!(
213 (
214 method.starts_with("notifications/"),
215 is_standard_notification(method)
216 ),
217 (true, false)
218 )
219}
220
221fn write_method_not_found(id: &serde_json::Value, method: &str, protocol: Option<WireProtocol>) {
225 let response = serde_json::json!({
226 "jsonrpc": "2.0",
227 "id": id,
228 "error": {
229 "code": -32601,
230 "message": format!("Method not found: {method}")
231 }
232 });
233 let body = serde_json::to_string(&response).unwrap_or_default();
234 let mut out = std::io::stdout().lock();
235 use std::io::Write;
236 let _ = if let Some(WireProtocol::ContentLength) = protocol {
237 write!(out, "Content-Length: {}\r\n\r\n{}", body.len(), body)
238 } else {
239 writeln!(out, "{body}")
240 };
241 let _ = std::io::Write::flush(&mut out);
242 tracing::debug!("Replied -32601 MethodNotFound for '{method}'");
243}
244
245fn try_parse_with_compatibility<T: DeserializeOwned>(
246 payload: &[u8],
247 context: &str,
248 protocol: Option<WireProtocol>,
249) -> Result<Option<T>, HybridCodecError> {
250 if let Ok(line_str) = std::str::from_utf8(payload) {
251 match serde_json::from_slice(payload) {
252 Ok(item) => Ok(Some(item)),
253 Err(error) => {
254 if let Ok(json_value) = serde_json::from_str::<serde_json::Value>(line_str)
255 && let Some(method) =
256 json_value.get("method").and_then(serde_json::Value::as_str)
257 {
258 if should_ignore_notification(&json_value, method) {
259 return Ok(None);
260 }
261 if let Some(id) = json_value.get("id") {
263 if !is_standard_method(method) {
264 write_method_not_found(id, method, protocol);
265 return Ok(None);
266 }
267 }
268 }
269
270 tracing::debug!(
271 "Failed to parse message {}: {} | Error: {}",
272 context,
273 line_str,
274 error
275 );
276 Err(HybridCodecError::Serde(error))
277 }
278 }
279 } else {
280 serde_json::from_slice(payload)
281 .map(Some)
282 .map_err(HybridCodecError::Serde)
283 }
284}
285
286#[derive(Debug, Error)]
287pub enum HybridCodecError {
288 #[error("max line length exceeded")]
289 MaxLineLengthExceeded,
290 #[error("missing Content-Length header")]
291 MissingContentLength,
292 #[error("invalid Content-Length value: {0}")]
293 InvalidContentLength(String),
294 #[error("invalid header frame: {0}")]
295 InvalidHeaderFrame(String),
296 #[error("serde error {0}")]
297 Serde(#[from] serde_json::Error),
298 #[error("io error {0}")]
299 Io(#[from] std::io::Error),
300}
301
302impl From<HybridCodecError> for std::io::Error {
303 fn from(value: HybridCodecError) -> Self {
304 match value {
305 HybridCodecError::MaxLineLengthExceeded
306 | HybridCodecError::MissingContentLength
307 | HybridCodecError::InvalidContentLength(_)
308 | HybridCodecError::InvalidHeaderFrame(_) => {
309 std::io::Error::new(std::io::ErrorKind::InvalidData, value)
310 }
311 HybridCodecError::Serde(error) => error.into(),
312 HybridCodecError::Io(error) => error,
313 }
314 }
315}
316
317fn looks_like_content_length_frame(buf: &BytesMut) -> bool {
318 let prefix = &buf[..buf.len().min(32)];
319 prefix
320 .windows(b"content-length".len())
321 .next()
322 .is_some_and(|candidate| candidate.eq_ignore_ascii_case(b"content-length"))
323}
324
325fn find_header_terminator(buf: &BytesMut) -> Option<(usize, usize)> {
326 if let Some(index) = buf.windows(4).position(|window| window == b"\r\n\r\n") {
327 return Some((index, 4));
328 }
329 buf.windows(2)
330 .position(|window| window == b"\n\n")
331 .map(|index| (index, 2))
332}
333
334fn parse_content_length(header: &str) -> Result<usize, HybridCodecError> {
335 for raw_line in header.lines() {
336 let line = raw_line.trim_end_matches('\r');
337 let Some((name, value)) = line.split_once(':') else {
338 continue;
339 };
340 if name.trim().eq_ignore_ascii_case("content-length") {
341 return value
342 .trim()
343 .parse::<usize>()
344 .map_err(|_| HybridCodecError::InvalidContentLength(value.trim().to_string()));
345 }
346 }
347
348 Err(HybridCodecError::MissingContentLength)
349}
350
351impl<T: DeserializeOwned> HybridJsonRpcMessageCodec<T> {
352 fn decode_content_length(&mut self, buf: &mut BytesMut) -> Result<Option<T>, HybridCodecError> {
353 loop {
356 let Some((header_end, delimiter_len)) = find_header_terminator(buf) else {
357 return Ok(None);
358 };
359
360 let header = std::str::from_utf8(&buf[..header_end])
361 .map_err(|error| HybridCodecError::InvalidHeaderFrame(error.to_string()))?;
362 let content_length = parse_content_length(header)?;
363 if content_length > self.max_length {
364 return Err(HybridCodecError::MaxLineLengthExceeded);
365 }
366 let body_start = header_end + delimiter_len;
367 let frame_len = body_start
368 .checked_add(content_length)
369 .ok_or(HybridCodecError::MaxLineLengthExceeded)?;
370 if buf.len() < frame_len {
371 return Ok(None);
372 }
373
374 let frame = buf.split_to(frame_len);
375 let payload = &frame[body_start..];
376 self.protocol.set_if_unset(WireProtocol::ContentLength);
377
378 match try_parse_with_compatibility(
379 payload,
380 "decode_content_length",
381 self.protocol.get(),
382 ) {
383 Ok(Some(item)) => return Ok(Some(item)),
384 Ok(None) => {}
386 Err(err) => {
390 tracing::warn!("skipping malformed Content-Length frame: {err}");
391 }
392 }
393 }
394 }
395
396 fn decode_json_line(&mut self, buf: &mut BytesMut) -> Result<Option<T>, HybridCodecError> {
397 loop {
398 let read_to = std::cmp::min(self.max_length.saturating_add(1), buf.len());
399 let newline_offset = buf[self.next_index..read_to]
400 .iter()
401 .position(|byte| *byte == b'\n');
402
403 match (self.is_discarding, newline_offset) {
404 (true, Some(offset)) => {
405 buf.advance(offset + self.next_index + 1);
406 self.is_discarding = false;
407 self.next_index = 0;
408 }
409 (true, None) => {
410 buf.advance(read_to);
411 self.next_index = 0;
412 if buf.is_empty() {
413 return Ok(None);
414 }
415 }
416 (false, Some(offset)) => {
417 let newline_index = offset + self.next_index;
418 self.next_index = 0;
419 let line = buf.split_to(newline_index + 1);
420 let line = &line[..line.len() - 1];
421 let payload = without_carriage_return(line);
422 self.protocol.set_if_unset(WireProtocol::JsonLine);
423
424 match try_parse_with_compatibility(
425 payload,
426 "decode_json_line",
427 self.protocol.get(),
428 ) {
429 Ok(Some(item)) => return Ok(Some(item)),
430 Ok(None) => {}
432 Err(err) => {
441 tracing::warn!("skipping malformed JSON-RPC line: {err}");
442 }
443 }
444 }
445 (false, None) if buf.len() > self.max_length => {
446 self.is_discarding = true;
447 return Err(HybridCodecError::MaxLineLengthExceeded);
448 }
449 (false, None) => {
450 self.next_index = read_to;
451 return Ok(None);
452 }
453 }
454 }
455 }
456}
457
458impl<T: DeserializeOwned> Decoder for HybridJsonRpcMessageCodec<T> {
459 type Item = T;
460 type Error = HybridCodecError;
461
462 fn decode(&mut self, buf: &mut BytesMut) -> Result<Option<T>, HybridCodecError> {
463 match self.protocol.get() {
464 Some(WireProtocol::ContentLength) => self.decode_content_length(buf),
465 Some(WireProtocol::JsonLine) => self.decode_json_line(buf),
466 None => {
467 if looks_like_content_length_frame(buf) {
468 self.decode_content_length(buf)
469 } else {
470 self.decode_json_line(buf)
471 }
472 }
473 }
474 }
475
476 fn decode_eof(&mut self, buf: &mut BytesMut) -> Result<Option<T>, HybridCodecError> {
477 match self.protocol.get() {
478 Some(WireProtocol::ContentLength) if !buf.is_empty() => self.decode_content_length(buf),
479 _ => Ok(if let Some(frame) = self.decode(buf)? {
480 Some(frame)
481 } else {
482 self.next_index = 0;
483 if buf.is_empty() || buf == &b"\r"[..] {
484 None
485 } else {
486 let line = buf.split_to(buf.len());
487 let payload = without_carriage_return(&line);
488 match try_parse_with_compatibility(payload, "decode_eof", self.protocol.get()) {
491 Ok(item) => item,
492 Err(err) => {
493 tracing::warn!("discarding malformed trailing frame at EOF: {err}");
494 None
495 }
496 }
497 }
498 }),
499 }
500 }
501}
502
503impl<T: Serialize> Encoder<T> for HybridJsonRpcMessageCodec<T> {
504 type Error = HybridCodecError;
505
506 fn encode(&mut self, item: T, buf: &mut BytesMut) -> Result<(), HybridCodecError> {
507 let payload = serde_json::to_vec(&item)?;
508
509 match self.protocol.get().unwrap_or(WireProtocol::ContentLength) {
510 WireProtocol::ContentLength => {
511 buf.extend_from_slice(
512 format!("Content-Length: {}\r\n\r\n", payload.len()).as_bytes(),
513 );
514 buf.extend_from_slice(&payload);
515 }
516 WireProtocol::JsonLine => {
517 buf.extend_from_slice(&payload);
518 buf.put_u8(b'\n');
519 }
520 }
521
522 Ok(())
523 }
524}
525
526#[cfg(test)]
527mod tests {
528 use super::*;
529
530 use tokio_util::bytes::BytesMut;
531
532 fn sample_message() -> serde_json::Value {
533 serde_json::json!({
534 "jsonrpc": "2.0",
535 "id": 1,
536 "method": "initialize",
537 "params": {
538 "protocolVersion": "2024-11-05",
539 "capabilities": {},
540 "clientInfo": {
541 "name": "probe",
542 "version": "0.0.0"
543 }
544 }
545 })
546 }
547
548 #[test]
549 fn decodes_json_line_and_marks_protocol() {
550 let protocol = SharedProtocol::new();
551 let mut codec = HybridJsonRpcMessageCodec::<serde_json::Value>::new(protocol.clone());
552 let payload = serde_json::to_vec(&sample_message()).unwrap();
553 let mut buf = BytesMut::from(&payload[..]);
554 buf.put_u8(b'\n');
555
556 let item = codec.decode(&mut buf).unwrap();
557 assert!(item.is_some());
558 assert_eq!(protocol.get(), Some(WireProtocol::JsonLine));
559 }
560
561 #[test]
562 fn decodes_content_length_and_marks_protocol() {
563 let protocol = SharedProtocol::new();
564 let mut codec = HybridJsonRpcMessageCodec::<serde_json::Value>::new(protocol.clone());
565 let payload = serde_json::to_vec(&sample_message()).unwrap();
566 let mut frame = BytesMut::new();
567 frame.extend_from_slice(format!("Content-Length: {}\r\n\r\n", payload.len()).as_bytes());
568 frame.extend_from_slice(&payload);
569
570 let item = codec.decode(&mut frame).unwrap();
571 assert!(item.is_some());
572 assert_eq!(protocol.get(), Some(WireProtocol::ContentLength));
573 }
574
575 #[test]
576 fn encodes_using_content_length_when_protocol_is_detected() {
577 let protocol = SharedProtocol::new();
578 protocol.set_if_unset(WireProtocol::ContentLength);
579 let mut codec = HybridJsonRpcMessageCodec::<serde_json::Value>::new(protocol);
580 let mut buf = BytesMut::new();
581 codec
582 .encode(
583 serde_json::json!({"jsonrpc":"2.0","id":1,"result":{"ok":true}}),
584 &mut buf,
585 )
586 .unwrap();
587
588 assert!(
589 std::str::from_utf8(&buf)
590 .unwrap()
591 .starts_with("Content-Length: ")
592 );
593 }
594
595 #[test]
596 fn decode_skips_malformed_json_line_and_recovers() {
597 let protocol = SharedProtocol::new();
601 let mut codec = HybridJsonRpcMessageCodec::<serde_json::Value>::new(protocol);
602 let good = serde_json::to_vec(&sample_message()).unwrap();
603 let mut buf = BytesMut::new();
604 buf.extend_from_slice(b"{ this is : not valid json }\n");
605 buf.extend_from_slice(&good);
606 buf.put_u8(b'\n');
607
608 let item = codec
609 .decode(&mut buf)
610 .expect("a malformed line must not be a hard transport error");
611 assert!(item.is_some(), "valid frame after a bad line must decode");
612 }
613
614 #[test]
615 fn decode_malformed_json_line_alone_is_not_a_transport_error() {
616 let protocol = SharedProtocol::new();
618 let mut codec = HybridJsonRpcMessageCodec::<serde_json::Value>::new(protocol);
619 let mut buf = BytesMut::from(&b"{ not valid json }\n"[..]);
620
621 let item = codec
622 .decode(&mut buf)
623 .expect("a malformed line must not be a hard transport error");
624 assert!(item.is_none());
625 }
626
627 #[test]
628 fn decode_skips_malformed_content_length_frame_and_recovers() {
629 let protocol = SharedProtocol::new();
632 protocol.set_if_unset(WireProtocol::ContentLength);
633 let mut codec = HybridJsonRpcMessageCodec::<serde_json::Value>::new(protocol);
634
635 let bad_body = b"{ not json }";
636 let good_body = serde_json::to_vec(&sample_message()).unwrap();
637 let mut buf = BytesMut::new();
638 buf.extend_from_slice(format!("Content-Length: {}\r\n\r\n", bad_body.len()).as_bytes());
639 buf.extend_from_slice(bad_body);
640 buf.extend_from_slice(format!("Content-Length: {}\r\n\r\n", good_body.len()).as_bytes());
641 buf.extend_from_slice(&good_body);
642
643 let item = codec
644 .decode(&mut buf)
645 .expect("a malformed CL frame must not be a hard transport error");
646 assert!(item.is_some(), "valid CL frame after a bad one must decode");
647 }
648}