use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};
use futures::{Sink, Stream};
use pin_project_lite::pin_project;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, AsyncWrite, BufReader};
use tokio::net::{TcpListener, TcpStream};
#[cfg(feature = "mcp")]
use rmcp::{
RoleClient, RoleServer,
service::{RxJsonRpcMessage, ServiceRole, TxJsonRpcMessage},
};
use super::transport_enhanced::{EnhancedTransport, TransportStats, TransportError};
pin_project! {
pub struct TcpTransport<R> {
#[pin]
reader: BufReader<tokio::io::ReadHalf<TcpStream>>,
#[pin]
writer: tokio::io::WriteHalf<TcpStream>,
stats: TransportStats,
address: String,
buffer: String,
marker: PhantomData<R>,
}
}
impl<R> TcpTransport<R>
where
R: ServiceRole,
{
pub async fn connect(host: &str, port: u16) -> Result<Self, TransportError> {
let addr = format!("{}:{}", host, port);
let stream = TcpStream::connect(&addr).await?;
Self::from_stream(stream, addr).await
}
pub async fn from_stream(stream: TcpStream, address: String) -> Result<Self, TransportError> {
let (read_half, write_half) = tokio::io::split(stream);
let reader = BufReader::new(read_half);
Ok(Self {
reader,
writer: write_half,
stats: TransportStats::default(),
address,
buffer: String::new(),
marker: PhantomData,
})
}
fn stats_mut(&mut self) -> &mut TransportStats {
&mut self.stats
}
}
impl<R> EnhancedTransport<R> for TcpTransport<R>
where
R: ServiceRole,
{
fn description(&self) -> String {
format!("TCP transport ({})", self.address)
}
fn is_connected(&self) -> bool {
true
}
fn stats(&self) -> TransportStats {
self.stats.clone()
}
}
#[cfg(feature = "mcp")]
impl<R> Stream for TcpTransport<R>
where
R: ServiceRole,
{
type Item = RxJsonRpcMessage<R>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
let this = self.as_mut().project();
match std::task::Poll::Pending::<Result<usize, std::io::Error>> {
Poll::Ready(Ok(0)) => {
tracing::info!("TCP connection closed");
Poll::Ready(None)
}
Poll::Ready(Ok(_)) => {
this.stats.messages_received += 1;
let line = this.buffer.trim_end().to_string();
this.buffer.clear();
if line.is_empty() {
return self.poll_next(cx);
}
match serde_json::from_str::<RxJsonRpcMessage<R>>(&line) {
Ok(message) => Poll::Ready(Some(message)),
Err(e) => {
tracing::warn!(error = %e, message = %line, "Failed to parse JSON-RPC message");
this.stats.connection_errors += 1;
this.stats.last_error = Some(e.to_string());
self.poll_next(cx)
}
}
}
Poll::Ready(Err(e)) => {
tracing::warn!(error = %e, "TCP read error");
let this = self.as_mut().project();
this.stats.connection_errors += 1;
this.stats.last_error = Some(e.to_string());
self.poll_next(cx)
}
Poll::Pending => Poll::Pending,
}
}
}
#[cfg(feature = "mcp")]
impl<R> Sink<TxJsonRpcMessage<R>> for TcpTransport<R>
where
R: ServiceRole,
{
type Error = std::io::Error;
fn poll_ready(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn start_send(
mut self: Pin<&mut Self>,
item: TxJsonRpcMessage<R>,
) -> Result<(), Self::Error> {
self.stats_mut().messages_sent += 1;
let this = self.project();
let message_text = serde_json::to_string(&item)
.expect("JSON-RPC message should be valid JSON");
let message_with_newline = format!("{}\n", message_text);
Ok(())
}
fn poll_flush(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
let this = self.project();
this.writer.poll_flush(cx)
}
fn poll_close(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), Self::Error>> {
let this = self.project();
this.writer.poll_shutdown(cx)
}
}
pub struct TcpServer {
listener: TcpListener,
}
impl TcpServer {
pub async fn bind(addr: &str) -> Result<Self, TransportError> {
let listener = TcpListener::bind(addr).await?;
Ok(Self { listener })
}
pub async fn accept<R>(&self) -> Result<TcpTransport<R>, TransportError>
where
R: ServiceRole,
{
let (stream, addr) = self.listener.accept().await?;
TcpTransport::from_stream(stream, addr.to_string()).await
}
pub fn local_addr(&self) -> Result<std::net::SocketAddr, std::io::Error> {
self.listener.local_addr()
}
}
pub struct TcpClient;
impl TcpClient {
pub async fn connect<R>(host: &str, port: u16) -> Result<TcpTransport<R>, TransportError>
where
R: ServiceRole,
{
TcpTransport::connect(host, port).await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tcp_transport_creation() {
}
#[tokio::test]
async fn test_tcp_server_bind() {
let server = TcpServer::bind("127.0.0.1:0").await.unwrap();
let addr = server.local_addr().unwrap();
assert!(addr.port() > 0);
}
#[test]
fn test_transport_stats() {
let stats = TransportStats::default();
assert_eq!(stats.messages_sent, 0);
assert_eq!(stats.messages_received, 0);
assert_eq!(stats.connection_errors, 0);
assert!(stats.last_error.is_none());
}
}