use crate::client_objectiveai_mcp::{
client_request, client_response, server_response,
};
use futures::SinkExt;
use futures::stream::SplitSink;
use std::sync::Arc;
use tokio::net::TcpStream;
use tokio::sync::{Mutex, oneshot};
use tokio_tungstenite::{MaybeTlsStream, WebSocketStream, tungstenite};
pub(crate) type SharedSink = Arc<
Mutex<
SplitSink<
WebSocketStream<MaybeTlsStream<TcpStream>>,
tungstenite::Message,
>,
>,
>;
pub(crate) type PendingNotifies =
Arc<dashmap::DashMap<String, oneshot::Sender<client_response::Response>>>;
#[derive(Clone)]
pub struct Notifier {
sink: SharedSink,
pending: PendingNotifies,
}
impl Notifier {
pub(crate) fn new(sink: SharedSink, pending: PendingNotifies) -> Self {
Self { sink, pending }
}
pub async fn notify_list_changed(
&self,
change: client_request::McpListChanged,
) -> Result<(), super::HttpError> {
match self
.send_raw(client_request::Payload::McpListChanged(change))
.await?
{
client_response::Response::Ok { .. } => Ok(()),
client_response::Response::Error { code, message, .. } => {
Err(super::HttpError::NotifyRejected { code, message })
}
_ => Err(super::HttpError::NotifyRejected {
code: 0,
message: serde_json::Value::String(
"unexpected reply variant for notify".to_string(),
),
}),
}
}
pub async fn list_tools(
&self,
response_id: String,
params: crate::mcp::tool::ListToolsRequest,
) -> Result<
server_response::JsonRpcResult<crate::mcp::tool::ListToolsResult>,
super::HttpError,
> {
match self
.send_raw(client_request::Payload::ListTools {
response_id,
params,
})
.await?
{
client_response::Response::ListTools { result, .. } => Ok(result),
other => Self::fold_unexpected(other),
}
}
pub async fn call_tool(
&self,
response_id: String,
params: crate::mcp::tool::CallToolRequestParams,
) -> Result<
server_response::JsonRpcResult<crate::mcp::tool::CallToolResult>,
super::HttpError,
> {
match self
.send_raw(client_request::Payload::CallTool {
response_id,
params,
})
.await?
{
client_response::Response::CallTool { result, .. } => Ok(result),
other => Self::fold_unexpected(other),
}
}
pub async fn list_resources(
&self,
response_id: String,
params: crate::mcp::resource::ListResourcesRequest,
) -> Result<
server_response::JsonRpcResult<crate::mcp::resource::ListResourcesResult>,
super::HttpError,
> {
match self
.send_raw(client_request::Payload::ListResources {
response_id,
params,
})
.await?
{
client_response::Response::ListResources { result, .. } => {
Ok(result)
}
other => Self::fold_unexpected(other),
}
}
pub async fn read_resource(
&self,
response_id: String,
params: crate::mcp::resource::ReadResourceRequestParams,
) -> Result<
server_response::JsonRpcResult<crate::mcp::resource::ReadResourceResult>,
super::HttpError,
> {
match self
.send_raw(client_request::Payload::ReadResource {
response_id,
params,
})
.await?
{
client_response::Response::ReadResource { result, .. } => {
Ok(result)
}
other => Self::fold_unexpected(other),
}
}
fn fold_unexpected<R>(
other: client_response::Response,
) -> Result<server_response::JsonRpcResult<R>, super::HttpError> {
match other {
client_response::Response::Error { code, message, .. } => {
let message = match message {
serde_json::Value::String(s) => s,
other => other.to_string(),
};
Ok(server_response::JsonRpcResult::Err {
code: code as i64,
message,
data: None,
})
}
_ => Err(super::HttpError::NotifyRejected {
code: 0,
message: serde_json::Value::String(
"unexpected reply variant for mcp request".to_string(),
),
}),
}
}
async fn send_raw(
&self,
payload: client_request::Payload,
) -> Result<client_response::Response, super::HttpError> {
let id = uuid::Uuid::new_v4().to_string();
let (tx, rx) = oneshot::channel();
self.pending.insert(id.clone(), tx);
let request = client_request::Request {
id: id.clone(),
payload,
};
let frame = match serde_json::to_string(&request) {
Ok(s) => s,
Err(e) => {
self.pending.remove(&id);
return Err(super::HttpError::NotifySerialize(e));
}
};
{
let mut guard = self.sink.lock().await;
if let Err(e) =
guard.send(tungstenite::Message::Text(frame.into())).await
{
drop(guard);
self.pending.remove(&id);
return Err(super::HttpError::NotifySend(e));
}
}
match rx.await {
Ok(r) => Ok(r),
Err(_) => Err(super::HttpError::NotifyChannelClosed),
}
}
}