use std::process::Stdio; use std::sync::Arc; use jsonrpsee::core::async_trait; use jsonrpsee::core::client::{ Client, ClientBuilder, ClientT, ReceivedMessage, TransportReceiverT, TransportSenderT, }; use jsonrpsee::core::params::ObjectParams; use jsonrpsee::core::traits::ToRpcParams; use tokio::process::{Child, ChildStdin, ChildStdout}; use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader}; use serde::{Deserialize, Serialize}; use serde_json::Error; use serde_json::value::RawValue; use tokio::time::sleep; use tracing::debug; use types::{Implementation, InitializeRequestParams, InitializeResult}; use crate::types::{ClientCapabilities, ListToolsRequestParams, ListToolsResult}; mod types; #[derive(Debug, Clone)] struct StdioTransport { stdin: Arc>, stdout: Arc>>, } impl StdioTransport { fn new(mut child: Child) -> Self { let stdin = Arc::new(tokio::sync::Mutex::new(child.stdin.take().unwrap())); let stdout = Arc::new(tokio::sync::Mutex::new(BufReader::new(child.stdout.take().unwrap()))); Self { stdin, stdout } } } #[async_trait] impl TransportSenderT for StdioTransport { type Error = tokio::io::Error; #[tracing::instrument(skip(self), level = "trace")] async fn send(&mut self, msg: String) -> Result<(), Self::Error> { debug!("Sending: {}", msg); let mut stdin = self.stdin.lock().await; stdin.write_all(msg.as_bytes()).await?; stdin.write_all(b"\n").await?; Ok(()) } } #[async_trait] impl TransportReceiverT for StdioTransport { type Error = tokio::io::Error; #[tracing::instrument(skip(self), level = "trace")] async fn receive(&mut self) -> Result { let mut stdout = self.stdout.lock().await; let mut str = String::new(); stdout.read_line(&mut str).await?; debug!("Received: {}", str); Ok(ReceivedMessage::Text(str)) } } struct RpcArg(T); impl ToRpcParams for RpcArg { fn to_rpc_params(self) -> Result>, Error> { let s = String::from_utf8(serde_json::to_vec(&self.0)?).expect("Valid UTF8 format"); RawValue::from_string(s).map(Some) } } struct NoParams; impl ToRpcParams for NoParams { fn to_rpc_params(self) -> Result>, Error> { Ok(None) } } #[tokio::main] async fn main() -> anyhow::Result<()> { tracing_subscriber::fmt() .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) .with_file(true) .with_line_number(true) .with_thread_ids(true) .with_thread_names(true) .with_target(true) .with_level(true) .init(); let cmd = tokio::process::Command::new("/Users/joshuacoles/.local/bin/mcp-server-fetch") .stdin(Stdio::piped()) .stdout(Stdio::piped()) .spawn()?; let transport = StdioTransport::new(cmd); let client: Client = ClientBuilder::default().build_with_tokio( transport.clone(), transport.clone(), ); let response: InitializeResult = client.request("initialize", RpcArg(InitializeRequestParams { capabilities: ClientCapabilities::default(), client_info: Implementation { name: "Rust MCP".to_string(), version: "0.1.0".to_string() }, protocol_version: "2024-11-05".to_string(), })).await?; println!("Response: {:?}", response); client.notification("notifications/initialized", NoParams).await?; let response: ListToolsResult = client.request("tools/list", RpcArg(ListToolsRequestParams::default())).await?; println!("Response: {:#?}", response); Ok(()) }