use std::collections::HashMap;
use r402::facilitator::BoxFuture;
use r402::proto;
use serde::{Deserialize, Serialize};
use crate::error::McpPaymentError;
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CallToolParams {
pub name: String,
#[serde(default)]
pub arguments: serde_json::Map<String, serde_json::Value>,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<serde_json::Map<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
#[non_exhaustive]
pub enum ContentItem {
Text {
text: String,
},
}
impl ContentItem {
#[must_use]
pub fn text(text: impl Into<String>) -> Self {
Self::Text { text: text.into() }
}
#[must_use]
pub fn as_text(&self) -> Option<&str> {
match self {
Self::Text { text } => Some(text),
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CallToolResult {
#[serde(default)]
pub content: Vec<ContentItem>,
#[serde(default, rename = "isError")]
pub is_error: bool,
#[serde(rename = "_meta", default, skip_serializing_if = "Option::is_none")]
pub meta: Option<serde_json::Map<String, serde_json::Value>>,
#[serde(
default,
rename = "structuredContent",
skip_serializing_if = "Option::is_none"
)]
pub structured_content: Option<serde_json::Value>,
}
#[derive(Debug, Clone)]
pub struct PaidToolCallResult {
pub content: Vec<ContentItem>,
pub is_error: bool,
pub payment_response: Option<proto::SettleResponse>,
pub payment_made: bool,
pub raw_result: CallToolResult,
}
#[derive(Debug, Clone)]
pub struct ToolCallContext {
pub tool_name: String,
pub arguments: serde_json::Map<String, serde_json::Value>,
pub meta: Option<serde_json::Map<String, serde_json::Value>>,
}
#[derive(Debug, Clone)]
pub struct PaymentRequiredContext {
pub tool_name: String,
pub arguments: serde_json::Map<String, serde_json::Value>,
pub payment_required: proto::PaymentRequired,
}
#[derive(Debug, Clone)]
pub struct BeforePaymentContext {
pub tool_name: String,
pub payment_required: proto::PaymentRequired,
}
#[derive(Debug, Clone)]
pub struct AfterPaymentContext {
pub tool_name: String,
pub payment_payload: serde_json::Value,
pub result: CallToolResult,
pub settle_response: Option<proto::SettleResponse>,
}
#[derive(Debug, Clone, Copy)]
pub struct ClientOptions {
pub auto_payment: bool,
}
impl Default for ClientOptions {
fn default() -> Self {
Self { auto_payment: true }
}
}
pub trait ClientHooks: Send + Sync {
fn on_payment_required(
&self,
_ctx: &PaymentRequiredContext,
) -> BoxFuture<'_, Result<Option<serde_json::Value>, McpPaymentError>> {
Box::pin(async { Ok(None) })
}
fn on_payment_requested(
&self,
_ctx: &PaymentRequiredContext,
) -> BoxFuture<'_, Result<bool, McpPaymentError>> {
Box::pin(async { Ok(true) })
}
fn on_before_payment(
&self,
_ctx: &BeforePaymentContext,
) -> BoxFuture<'_, Result<(), McpPaymentError>> {
Box::pin(async { Ok(()) })
}
fn on_after_payment(
&self,
_ctx: &AfterPaymentContext,
) -> BoxFuture<'_, Result<(), McpPaymentError>> {
Box::pin(async { Ok(()) })
}
}
#[derive(Debug, Clone, Copy)]
pub struct NoClientHooks;
impl ClientHooks for NoClientHooks {}
#[derive(Debug, Clone)]
pub struct ServerHookContext {
pub tool_name: String,
pub arguments: serde_json::Map<String, serde_json::Value>,
pub payment_requirements: proto::v2::PaymentRequirements,
pub payment_payload: serde_json::Value,
}
#[derive(Debug, Clone)]
pub struct AfterExecutionContext {
pub server_ctx: ServerHookContext,
pub result: CallToolResult,
}
#[derive(Debug, Clone)]
pub struct SettlementContext {
pub server_ctx: ServerHookContext,
pub settlement: proto::SettleResponse,
}
pub trait ServerHooks: Send + Sync {
fn on_before_execution(
&self,
_ctx: &ServerHookContext,
) -> BoxFuture<'_, Result<bool, McpPaymentError>> {
Box::pin(async { Ok(true) })
}
fn on_after_execution(
&self,
_ctx: &AfterExecutionContext,
) -> BoxFuture<'_, Result<(), McpPaymentError>> {
Box::pin(async { Ok(()) })
}
fn on_after_settlement(
&self,
_ctx: &SettlementContext,
) -> BoxFuture<'_, Result<(), McpPaymentError>> {
Box::pin(async { Ok(()) })
}
}
#[derive(Debug, Clone, Copy)]
pub struct NoServerHooks;
impl ServerHooks for NoServerHooks {}
pub struct PaymentWrapperConfig {
pub accepts: Vec<proto::v2::PaymentRequirements>,
pub resource: Option<proto::v2::ResourceInfo>,
pub hooks: Option<Box<dyn ServerHooks>>,
pub extensions: Option<HashMap<String, serde_json::Value>>,
}
#[allow(clippy::derivable_impls)]
impl Default for PaymentWrapperConfig {
fn default() -> Self {
Self {
accepts: Vec::new(),
resource: None,
hooks: None,
extensions: None,
}
}
}
impl std::fmt::Debug for PaymentWrapperConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PaymentWrapperConfig")
.field("accepts", &self.accepts)
.field("resource", &self.resource)
.field("hooks", &self.hooks.as_ref().map(|_| "<dyn ServerHooks>"))
.field("extensions", &self.extensions)
.finish()
}
}