use std::io::{self, Read, Write};
use std::pin::Pin;
use std::task::{Context, Poll};
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
use tokio::sync::mpsc;
pub struct StdinReader {
rx: mpsc::Receiver<Vec<u8>>,
pending: Vec<u8>,
pos: usize,
}
pub fn stdin_reader() -> StdinReader {
let (tx, rx) = mpsc::channel::<Vec<u8>>(8);
let spawned = std::thread::Builder::new()
.name("cyberbrain-mcp-stdin".into())
.spawn(move || {
let mut stdin = io::stdin().lock();
let mut buf = vec![0u8; 64 * 1024];
loop {
match stdin.read(&mut buf) {
Ok(0) => break,
Ok(n) => {
if tx.blocking_send(buf[..n].to_vec()).is_err() {
break; }
}
Err(e) if e.kind() == io::ErrorKind::Interrupted => continue,
Err(e) => {
eprintln!("cyberbrain mcp: stdin read failed: {e}");
break;
}
}
}
});
if let Err(e) = spawned {
eprintln!("cyberbrain mcp: cannot start the stdin thread: {e}");
}
StdinReader {
rx,
pending: Vec::new(),
pos: 0,
}
}
impl AsyncRead for StdinReader {
fn poll_read(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
buf: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
loop {
if self.pos < self.pending.len() {
let n = (self.pending.len() - self.pos).min(buf.remaining());
let start = self.pos;
buf.put_slice(&self.pending[start..start + n]);
self.pos += n;
return Poll::Ready(Ok(()));
}
match self.rx.poll_recv(cx) {
Poll::Ready(Some(chunk)) => {
self.pending = chunk;
self.pos = 0;
}
Poll::Ready(None) => return Poll::Ready(Ok(())), Poll::Pending => return Poll::Pending,
}
}
}
}
pub struct ProtocolStdout {
out: io::Stdout,
}
pub fn stdout_writer() -> ProtocolStdout {
ProtocolStdout { out: io::stdout() }
}
impl AsyncWrite for ProtocolStdout {
fn poll_write(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
buf: &[u8],
) -> Poll<io::Result<usize>> {
let mut lock = self.out.lock();
Poll::Ready(lock.write_all(buf).map(|()| buf.len()))
}
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
Poll::Ready(self.out.lock().flush())
}
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
self.poll_flush(cx)
}
}