use std::collections::HashMap;
use std::convert::Infallible;
use std::sync::{Arc, Mutex};
use agent_client_protocol::schema::v1::{
McpError, McpServer, McpServerAcp, McpServerAcpId, MessageMcpNotification, MessageMcpRequest, MessageMcpResponse,
};
use agent_client_protocol::{Agent, ConnectionTo, Dispatch, HandleDispatchFrom, Handled, Responder};
use rmcp::model::{ClientNotification, ClientRequest, JsonRpcMessage, RequestId};
use rmcp::service::{RoleServer, RxJsonRpcMessage, TxJsonRpcMessage};
use rmcp::transport::Transport;
use tokio::sync::mpsc;
use aves::auth::Jwt;
use aves::bus::BusRpcContext;
use aves::utils::buffers::DEFAULT_BUFFER_SIZE;
use super::boot::{DiscussMcpDomain, DiscussMcpService};
pub struct McpAcpTransport {
inbound: mpsc::Receiver<RxJsonRpcMessage<RoleServer>>,
outbound: mpsc::Sender<TxJsonRpcMessage<RoleServer>>,
}
struct McpAcpConnection {
inbound: mpsc::Sender<RxJsonRpcMessage<RoleServer>>,
responders: Arc<Mutex<HashMap<RequestId, Responder<serde_json::Value>>>>,
cancellation: tokio_util::sync::CancellationToken,
task: tokio::task::JoinHandle<()>,
}
pub struct McpAcpShim {
state: Arc<Mutex<McpAcpShimState>>,
}
struct McpAcpShimState {
closed: bool,
server_id: McpServerAcpId,
descriptor: McpServer,
service: Arc<dyn DiscussMcpService>,
context: BusRpcContext,
worker_jwt: Jwt,
connections: HashMap<McpServerAcpId, McpAcpConnection>,
}
impl Transport<RoleServer> for McpAcpTransport {
type Error = Infallible;
fn send(
&mut self,
message: TxJsonRpcMessage<RoleServer>,
) -> impl Future<Output = Result<(), Self::Error>> + Send + 'static {
let outbound = self.outbound.clone();
async move {
let _ = outbound.send(message).await;
Ok(())
}
}
async fn receive(&mut self) -> Option<RxJsonRpcMessage<RoleServer>> {
self.inbound.recv().await
}
fn close(&mut self) -> impl Future<Output = Result<(), Self::Error>> + Send {
std::future::ready(Ok(()))
}
}
impl McpAcpShim {
pub fn new(domain: &DiscussMcpDomain, _owner: String, context: BusRpcContext, worker_jwt: Jwt) -> Self {
let server_id = McpServerAcpId::new(format!("acp:{}", uuid::Uuid::new_v4()));
let descriptor = sluice_mcp_acp_descriptor(domain.server_name.to_string(), server_id.clone());
let state = McpAcpShimState {
closed: false,
server_id,
descriptor,
service: domain.service.clone(),
context,
worker_jwt,
connections: HashMap::new(),
};
let state = Arc::new(Mutex::new(state));
Self { state }
}
pub fn descriptor(&self) -> McpServer {
self.state
.lock()
.expect("MCP-over-ACP state lock")
.descriptor
.clone()
}
pub fn dynamic_handler(&self) -> McpAcpDynamicHandler {
McpAcpDynamicHandler {
state: self.state.clone(),
}
}
}
impl Drop for McpAcpShim {
fn drop(&mut self) {
let connections = {
let mut state = self.state.lock().expect("MCP-over-ACP state lock");
state.closed = true;
std::mem::take(&mut state.connections)
};
for connection in connections.into_values() {
close_mcp_acp_connection(&connection);
connection.task.abort();
}
}
}
pub struct McpAcpDynamicHandler {
state: Arc<Mutex<McpAcpShimState>>,
}
impl McpAcpDynamicHandler {
fn connect(&mut self) {
let mut state = self.state.lock().expect("MCP-over-ACP state lock");
if state.closed || state.connections.contains_key(&state.server_id) {
return;
}
let server_id = state.server_id.clone();
let (inbound, inbound_rx) = mpsc::channel(DEFAULT_BUFFER_SIZE);
let (outbound_tx, outbound_rx) = mpsc::channel(DEFAULT_BUFFER_SIZE);
let responders = Arc::new(Mutex::new(HashMap::new()));
let cancellation = tokio_util::sync::CancellationToken::new();
let task = tokio::spawn(run_mcp_acp_connection(
McpAcpTransport {
inbound: inbound_rx,
outbound: outbound_tx,
},
outbound_rx,
responders.clone(),
cancellation.clone(),
self.state.clone(),
server_id.clone(),
));
state.connections.insert(
server_id,
McpAcpConnection {
inbound,
responders,
cancellation,
task,
},
);
}
fn notification(&mut self, notification: MessageMcpNotification) -> Result<(), agent_client_protocol::Error> {
let mcp_notification = match mcp_notification(¬ification.method, notification.params) {
Ok(notification) => notification,
Err(_) => return Ok(()),
};
let state = self.state.lock().expect("MCP-over-ACP state lock");
let Some(connection) = state.connections.get(¬ification.server_id) else {
return Ok(());
};
let _ = connection
.inbound
.try_send(JsonRpcMessage::notification(mcp_notification));
Ok(())
}
fn message(
&mut self,
request: MessageMcpRequest,
responder: Responder<serde_json::Value>,
) -> Result<(), agent_client_protocol::Error> {
let mcp_request = match mcp_request(&request.method, request.params) {
Ok(request) => request,
Err(error) => {
responder.respond_with_internal_error(error.to_string())?;
return Ok(());
}
};
self.connect();
let state = self.state.lock().expect("MCP-over-ACP state lock");
let Some(connection) = state.connections.get(&request.server_id) else {
responder.respond_with_internal_error("MCP-over-ACP connection is not active")?;
return Ok(());
};
let request_id = RequestId::String(request.request_id.0);
let mut responders = connection
.responders
.lock()
.expect("MCP-over-ACP responder lock");
if responders.contains_key(&request_id) {
responder.respond_with_internal_error("MCP-over-ACP request is already active")?;
return Ok(());
}
responders.insert(request_id.clone(), responder);
drop(responders);
if connection
.inbound
.try_send(JsonRpcMessage::request(mcp_request, request_id.clone()))
.is_err()
&& let Some(responder) = connection
.responders
.lock()
.expect("MCP-over-ACP responder lock")
.remove(&request_id)
{
responder.respond_with_internal_error("MCP-over-ACP connection is closed")?;
}
Ok(())
}
}
impl HandleDispatchFrom<Agent> for McpAcpDynamicHandler {
fn describe_chain(&self) -> impl std::fmt::Debug {
"SluiceMcpAcpShim"
}
async fn handle_dispatch_from(
&mut self,
message: Dispatch,
_connection: ConnectionTo<Agent>,
) -> Result<Handled<Dispatch>, agent_client_protocol::Error> {
match message {
Dispatch::Request(request, responder) if request.method() == "mcp/message" => {
let message = match serde_json::from_value::<MessageMcpRequest>(request.params().clone()) {
Ok(message) => message,
Err(error) => {
responder.respond_with_internal_error(error)?;
return Ok(Handled::Yes);
}
};
if self
.state
.lock()
.expect("MCP-over-ACP state lock")
.server_id
!= message.server_id
{
return Ok(Handled::No {
message: Dispatch::Request(request, responder),
retry: false,
});
}
self.message(message, responder)?;
Ok(Handled::Yes)
}
Dispatch::Notification(notification) if notification.method() == "mcp/message" => {
let message = match serde_json::from_value::<MessageMcpNotification>(notification.params().clone()) {
Ok(message) => message,
Err(_) => return Ok(Handled::Yes),
};
if self
.state
.lock()
.expect("MCP-over-ACP state lock")
.server_id
!= message.server_id
{
return Ok(Handled::No {
message: Dispatch::Notification(notification),
retry: false,
});
}
self.notification(message)?;
Ok(Handled::Yes)
}
message => Ok(Handled::No { message, retry: false }),
}
}
}
pub fn sluice_mcp_acp_descriptor(name: String, server_id: McpServerAcpId) -> McpServer {
McpServer::Acp(McpServerAcp::new(name, server_id))
}
async fn run_mcp_acp_connection(
transport: McpAcpTransport,
mut outbound: mpsc::Receiver<TxJsonRpcMessage<RoleServer>>,
responders: Arc<Mutex<HashMap<RequestId, Responder<serde_json::Value>>>>,
cancellation: tokio_util::sync::CancellationToken,
state: Arc<Mutex<McpAcpShimState>>,
server_id: McpServerAcpId,
) {
let (service, context, worker_jwt) = {
let state = state.lock().expect("MCP-over-ACP state lock");
(state.service.clone(), state.context.clone(), state.worker_jwt.clone())
};
tokio::select! {
_ = async {
if let Some(running) = service
.start(transport, cancellation.clone(), context, worker_jwt)
.await
{
running.await;
}
} => {}
_ = async {
while let Some(message) = outbound.recv().await {
forward_mcp_acp_response(message, &responders);
}
} => {}
}
cancellation.cancel();
remove_mcp_acp_connection(&state, &server_id);
}
fn mcp_notification(
method: &str,
params: Option<serde_json::Map<String, serde_json::Value>>,
) -> Result<ClientNotification, serde_json::Error> {
serde_json::from_value(serde_json::json!({ "method": method, "params": params }))
}
fn mcp_request(
method: &str,
params: Option<serde_json::Map<String, serde_json::Value>>,
) -> Result<ClientRequest, serde_json::Error> {
serde_json::from_value(serde_json::json!({ "method": method, "params": params }))
}
fn forward_mcp_acp_response(
message: TxJsonRpcMessage<RoleServer>,
responders: &Mutex<HashMap<RequestId, Responder<serde_json::Value>>>,
) {
let Some((request_id, response)) = mcp_acp_response(message) else {
return;
};
if let Some(responder) = responders
.lock()
.expect("MCP-over-ACP responder lock")
.remove(&request_id)
{
let _ = responder.respond(serde_json::to_value(response).expect("MCP-over-ACP response is serializable"));
}
}
fn mcp_acp_response(message: TxJsonRpcMessage<RoleServer>) -> Option<(RequestId, MessageMcpResponse)> {
let response = match message {
JsonRpcMessage::Response(response) => (
response.id,
MessageMcpResponse::success(serde_json::to_value(response.result).expect("MCP result is serializable")),
),
JsonRpcMessage::Error(error) => {
let request_id = error.id?;
let mut inner = McpError::new(error.error.code.0, error.error.message);
if let Some(data) = error.error.data {
inner = inner.data(data);
}
(request_id, MessageMcpResponse::error(inner))
}
JsonRpcMessage::Request(_) | JsonRpcMessage::Notification(_) => return None,
};
Some(response)
}
fn close_mcp_acp_connection(connection: &McpAcpConnection) {
connection.cancellation.cancel();
for (_, responder) in connection
.responders
.lock()
.expect("MCP-over-ACP responder lock")
.drain()
{
let _ = responder.respond_with_internal_error("MCP-over-ACP connection is closed");
}
}
fn remove_mcp_acp_connection(state: &Arc<Mutex<McpAcpShimState>>, server_id: &McpServerAcpId) {
let connection = state
.lock()
.expect("MCP-over-ACP state lock")
.connections
.remove(server_id);
if let Some(connection) = connection {
close_mcp_acp_connection(&connection);
}
}