mcp-rb/ext/mcp/src/internal-test.rs
2025-03-14 21:31:12 +00:00

119 lines
3.7 KiB
Rust

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<tokio::sync::Mutex<ChildStdin>>,
stdout: Arc<tokio::sync::Mutex<BufReader<ChildStdout>>>,
}
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<ReceivedMessage, Self::Error> {
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>(T);
impl<T: Serialize> ToRpcParams for RpcArg<T> {
fn to_rpc_params(self) -> Result<Option<Box<RawValue>>, 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<Option<Box<RawValue>>, 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(())
}