Files
zesdex/apps/infrastructure/src/mcp/transport.rs
T
asepharyana 148ba4e07b feat(mcp): enhance MCP server registration with error handling and improve transport process management
refactor: update various tools for better error handling and path resolution
fix: improve markdown rendering and input display in TUI
2026-07-20 13:12:04 +07:00

98 lines
3.0 KiB
Rust

//! MCP transport layer — manages child-process and HTTP-based transport
//! for connecting to MCP servers.
use std::{
io::{Read, Write},
process::{Child, ChildStdin, ChildStdout, Command, Stdio},
};
/// A running MCP server process connected via stdio.
///
/// Holds the child process handle plus the piped stdin/stdout streams
/// so callers can send JSON-RPC messages and read responses.
pub struct McpTransport {
process: Option<Child>,
stdin: Option<ChildStdin>,
stdout: Option<ChildStdout>,
}
impl McpTransport {
/// Spawn a child process as an MCP server over stdio.
///
/// The command is passed to `sh -c` so shell syntax (pipes, redirects, etc.)
/// works naturally. Stderr is discarded to avoid corrupting a TUI that may
/// be running in the same terminal.
pub fn start_child_process(name: &str, command: &str) -> anyhow::Result<Self> {
tracing::info!("starting MCP transport '{name}': {command}");
let mut child = Command::new("sh")
.arg("-c")
.arg(command)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::null())
.spawn()?;
let stdin = child.stdin.take();
let stdout = child.stdout.take();
Ok(McpTransport {
process: Some(child),
stdin,
stdout,
})
}
/// Write raw bytes to the child's stdin.
pub fn send(&mut self, data: &[u8]) -> anyhow::Result<()> {
if let Some(ref mut stdin) = self.stdin {
stdin.write_all(data)?;
stdin.flush()?;
}
Ok(())
}
/// Read from the child's stdout into the provided buffer.
///
/// Returns `Ok(Some(n))` with the number of bytes read,
/// `Ok(None)` on EOF, or `Err` on I/O errors.
pub fn receive(&mut self, buf: &mut [u8]) -> anyhow::Result<Option<usize>> {
match self.stdout.as_mut() {
Some(stdout) => match stdout.read(buf) {
Ok(0) => Ok(None),
Ok(n) => Ok(Some(n)),
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None),
Err(e) => Err(e.into()),
},
None => Ok(None),
}
}
/// Gracefully shut down the child by closing stdin (sending EOF) and
/// then killing the process.
pub fn kill(&mut self) {
// Close stdin first to signal EOF to the MCP server.
let _ = self.stdin.take();
if let Some(ref mut child) = self.process {
let _ = child.kill();
let _ = child.wait();
}
}
/// Check whether the child process is still running.
pub fn is_running(&mut self) -> bool {
self.process
.as_mut()
.is_some_and(|c| matches!(c.try_wait(), Ok(None)))
}
/// Stop the child process. This is the public API alias for `kill`.
pub fn stop(&mut self) -> anyhow::Result<()> {
self.kill();
Ok(())
}
}
impl Drop for McpTransport {
fn drop(&mut self) {
self.kill();
}
}