laburnum 1.17.1

An LSP framework for building language servers and compilers, powered by an incremental query tree with content-addressed storage, task-based dataflow, and parallel queries.
Documentation
// Copyright Two Neutron Stars Incorporated and contributors
// SPDX-License-Identifier: BlueOak-1.0.0

//! MCP stdio transport.
//!
//! Unlike LSP (which uses HTTP-style `Content-Length` framing), the MCP
//! stdio transport is newline-delimited JSON: one JSON-RPC message per line,
//! no embedded newlines in the payload.

use {
  crate::protocol::jsonrpc::Message,
  smol::io::{
    AsyncBufReadExt,
    AsyncWriteExt,
  },
  std::io,
};

/// Reads one newline-delimited MCP message from `r`.
///
/// Returns `Ok(None)` on clean EOF. Blank lines are skipped (some clients
/// send them between messages for liveness).
pub async fn read_message<R>(r: &mut R) -> io::Result<Option<Message>>
where
  R: AsyncBufReadExt + Unpin,
{
  let mut line = String::new();
  loop {
    line.clear();
    let n = r.read_line(&mut line).await?;
    if n == 0 {
      return Ok(None);
    }
    let trimmed = line.trim_end_matches(['\r', '\n']);
    if trimmed.is_empty() {
      continue;
    }
    return serde_json::from_str(trimmed)
      .map(Some)
      .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e));
  }
}

/// Writes a single MCP message followed by a newline.
pub async fn write_message<W>(w: &mut W, msg: &Message) -> io::Result<()>
where
  W: AsyncWriteExt + Unpin,
{
  let text = serde_json::to_string(msg)?;
  w.write_all(text.as_bytes()).await?;
  w.write_all(b"\n").await?;
  w.flush().await
}