use std::borrow::Cow;
use std::future::Future;
use r402::facilitator::BoxFuture;
use rmcp::model as mcp;
use rmcp::service::{Peer, RoleClient};
use crate::client::McpCaller;
use crate::error::McpPaymentError;
use crate::types::{CallToolParams, CallToolResult, ContentItem};
#[must_use]
pub fn content_from_rmcp(content: &[mcp::Content]) -> Vec<ContentItem> {
content
.iter()
.filter_map(|c| {
let value = serde_json::to_value(c).ok()?;
let type_str = value.get("type")?.as_str()?;
if type_str == "text" {
let text = value.get("text")?.as_str()?;
Some(ContentItem::text(text))
} else {
None
}
})
.collect()
}
#[must_use]
pub fn content_to_rmcp(content: &[ContentItem]) -> Vec<mcp::Content> {
content
.iter()
.filter_map(|item| {
let ContentItem::Text { text } = item;
let value = serde_json::json!({"type": "text", "text": text});
serde_json::from_value(value).ok()
})
.collect()
}
#[must_use]
pub fn result_from_rmcp(result: &mcp::CallToolResult) -> CallToolResult {
CallToolResult {
content: content_from_rmcp(&result.content),
is_error: result.is_error.unwrap_or(false),
meta: result.meta.as_ref().map(|m| m.0.clone()),
structured_content: result.structured_content.clone(),
}
}
#[must_use]
pub fn result_to_rmcp(result: &CallToolResult) -> mcp::CallToolResult {
mcp::CallToolResult {
content: content_to_rmcp(&result.content),
is_error: Some(result.is_error),
meta: result.meta.as_ref().map(|m| mcp::Meta(m.clone())),
structured_content: result.structured_content.clone(),
}
}
impl From<mcp::CallToolRequestParams> for CallToolParams {
fn from(params: mcp::CallToolRequestParams) -> Self {
Self {
name: params.name.into_owned(),
arguments: params.arguments.unwrap_or_default(),
meta: params.meta.map(|m| m.0),
}
}
}
impl From<CallToolParams> for mcp::CallToolRequestParams {
fn from(params: CallToolParams) -> Self {
Self {
name: Cow::Owned(params.name),
arguments: if params.arguments.is_empty() {
None
} else {
Some(params.arguments)
},
meta: params.meta.map(mcp::Meta),
task: None,
}
}
}
impl McpCaller for Peer<RoleClient> {
fn call_tool(
&self,
params: CallToolParams,
) -> BoxFuture<'_, Result<CallToolResult, McpPaymentError>> {
Box::pin(async move {
let rmcp_params: mcp::CallToolRequestParams = params.into();
let rmcp_result = Self::call_tool(self, rmcp_params)
.await
.map_err(|e| McpPaymentError::ToolCallFailed(e.to_string()))?;
Ok(result_from_rmcp(&rmcp_result))
})
}
}
pub trait PaymentWrapperRmcpExt {
fn process_rmcp<H, Fut>(
&self,
request: mcp::CallToolRequestParams,
handler: H,
) -> impl Future<Output = mcp::CallToolResult> + Send
where
H: FnOnce(CallToolParams) -> Fut + Send,
Fut: Future<Output = Result<CallToolResult, McpPaymentError>> + Send;
}
impl PaymentWrapperRmcpExt for crate::server::PaymentWrapper {
#[allow(clippy::manual_async_fn)]
fn process_rmcp<H, Fut>(
&self,
request: mcp::CallToolRequestParams,
handler: H,
) -> impl Future<Output = mcp::CallToolResult> + Send
where
H: FnOnce(CallToolParams) -> Fut + Send,
Fut: Future<Output = Result<CallToolResult, McpPaymentError>> + Send,
{
async move {
let r402_params: CallToolParams = request.into();
let r402_result = self.process(r402_params, handler).await;
result_to_rmcp(&r402_result)
}
}
}