119 lines
3.7 KiB
Rust
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(())
|
|
}
|