use std::sync::atomic::{AtomicU64, Ordering};
use serde_json::{json, Value};
use super::transport::{JsonRpcMessage, McpError, Transport};
pub struct McpClient {
transport: Box<dyn Transport + Send + Sync>,
next_id: AtomicU64,
}
impl McpClient {
pub fn new<T: Transport + Send + Sync + 'static>(transport: T) -> Self {
Self {
transport: Box::new(transport),
next_id: AtomicU64::new(1),
}
}
fn next_id(&self) -> u64 {
self.next_id.fetch_add(1, Ordering::Relaxed)
}
pub async fn request(&mut self, method: &str, params: Value) -> Result<Value, McpError> {
let id = self.next_id();
let msg = JsonRpcMessage::request(id, method, params);
self.transport.send(msg).await?;
let reply = self.transport.recv().await?;
if !reply.is_response() {
return Err(McpError::Other(format!(
"expected response, got method={:?}",
reply.method
)));
}
if let Some(err) = reply.error {
return Err(McpError::Rpc {
code: err.code,
message: err.message,
});
}
Ok(reply.result.unwrap_or(Value::Null))
}
pub async fn notify(&mut self, method: &str, params: Value) -> Result<(), McpError> {
let msg = JsonRpcMessage::notification(method, params);
self.transport.send(msg).await
}
pub async fn initialize(&mut self) -> Result<Value, McpError> {
self.request(
"initialize",
json!({
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {
"name": "cognitum-rs",
"version": env!("CARGO_PKG_VERSION"),
},
}),
)
.await
}
pub async fn list_tools(&mut self) -> Result<Value, McpError> {
self.request("tools/list", json!({})).await
}
pub async fn call_tool(&mut self, name: &str, args: Value) -> Result<Value, McpError> {
self.request(
"tools/call",
json!({
"name": name,
"arguments": args,
}),
)
.await
}
pub async fn close(&mut self) -> Result<(), McpError> {
self.transport.close().await
}
}