use async_trait::async_trait;
use serde::Deserialize;
#[cfg(feature = "mcp-server")]
use schemars::JsonSchema;
use serde_json::Value as JsonValue;
use crate::integration::HostIntegration;
use super::router::{EnhancedTool, CallToolResult, Content, Parameters};
use super::{Permission, ToolError};
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "mcp-server", derive(JsonSchema))]
pub struct ArithmeticParams {
pub a: f64,
pub b: f64,
}
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "mcp-server", derive(JsonSchema))]
pub struct AdvancedMathParams {
pub value: f64,
pub precision: Option<u32>,
}
#[derive(Debug, Deserialize)]
#[cfg_attr(feature = "mcp-server", derive(JsonSchema))]
pub struct ExpressionParams {
pub expression: String,
}
#[derive(Clone)]
pub struct CalculatorTool {
name: String,
}
impl CalculatorTool {
pub fn new() -> Self {
Self {
name: "calculator".to_string(),
}
}
async fn add(&self, params: ArithmeticParams) -> Result<CallToolResult, ToolError> {
let result = params.a + params.b;
Ok(CallToolResult::success(vec![Content::text(format!(
"{} + {} = {}",
params.a, params.b, result
))]))
}
async fn subtract(&self, params: ArithmeticParams) -> Result<CallToolResult, ToolError> {
let result = params.a - params.b;
Ok(CallToolResult::success(vec![Content::text(format!(
"{} - {} = {}",
params.a, params.b, result
))]))
}
async fn multiply(&self, params: ArithmeticParams) -> Result<CallToolResult, ToolError> {
let result = params.a * params.b;
Ok(CallToolResult::success(vec![Content::text(format!(
"{} × {} = {}",
params.a, params.b, result
))]))
}
async fn divide(&self, params: ArithmeticParams) -> Result<CallToolResult, ToolError> {
if params.b == 0.0 {
return Ok(CallToolResult::error("Division by zero is not allowed".to_string()));
}
let result = params.a / params.b;
Ok(CallToolResult::success(vec![Content::text(format!(
"{} ÷ {} = {}",
params.a, params.b, result
))]))
}
async fn sqrt(&self, params: AdvancedMathParams) -> Result<CallToolResult, ToolError> {
if params.value < 0.0 {
return Ok(CallToolResult::error("Cannot calculate square root of negative number".to_string()));
}
let result = params.value.sqrt();
let precision = params.precision.unwrap_or(2);
let formatted_result = format!("{:.precision$}", result, precision = precision as usize);
Ok(CallToolResult::success(vec![Content::text(format!(
"√{} = {}",
params.value, formatted_result
))]))
}
async fn power(&self, params: ArithmeticParams) -> Result<CallToolResult, ToolError> {
let result = params.a.powf(params.b);
Ok(CallToolResult::success(vec![Content::text(format!(
"{}^{} = {}",
params.a, params.b, result
))]))
}
async fn evaluate(&self, params: ExpressionParams) -> Result<CallToolResult, ToolError> {
let expression = params.expression.trim();
if let Some(result) = self.evaluate_simple_expression(expression) {
Ok(CallToolResult::success(vec![Content::text(format!(
"{} = {}",
expression, result
))]))
} else {
Ok(CallToolResult::error(format!(
"Unable to evaluate expression: {}. Supported operations: +, -, *, /, sqrt(), ^",
expression
)))
}
}
fn evaluate_simple_expression(&self, expr: &str) -> Option<f64> {
if expr.contains('+') {
let parts: Vec<&str> = expr.split('+').collect();
if parts.len() == 2 {
let a = parts[0].trim().parse::<f64>().ok()?;
let b = parts[1].trim().parse::<f64>().ok()?;
return Some(a + b);
}
}
if expr.contains('-') && !expr.starts_with('-') {
let parts: Vec<&str> = expr.split('-').collect();
if parts.len() == 2 {
let a = parts[0].trim().parse::<f64>().ok()?;
let b = parts[1].trim().parse::<f64>().ok()?;
return Some(a - b);
}
}
if expr.contains('*') {
let parts: Vec<&str> = expr.split('*').collect();
if parts.len() == 2 {
let a = parts[0].trim().parse::<f64>().ok()?;
let b = parts[1].trim().parse::<f64>().ok()?;
return Some(a * b);
}
}
if expr.contains('/') {
let parts: Vec<&str> = expr.split('/').collect();
if parts.len() == 2 {
let a = parts[0].trim().parse::<f64>().ok()?;
let b = parts[1].trim().parse::<f64>().ok()?;
if b != 0.0 {
return Some(a / b);
}
}
}
expr.parse::<f64>().ok()
}
}
#[async_trait]
impl EnhancedTool for CalculatorTool {
async fn execute_enhanced(
&self,
parameters: JsonValue,
_host: &dyn HostIntegration,
) -> Result<CallToolResult, ToolError> {
let operation = parameters.get("operation")
.and_then(|v| v.as_str())
.ok_or_else(|| ToolError::InvalidParameters("Missing 'operation' parameter".to_string()))?;
match operation {
"add" => {
let params: Parameters<ArithmeticParams> = Parameters::from_json(parameters)?;
self.add(params.0).await
}
"subtract" => {
let params: Parameters<ArithmeticParams> = Parameters::from_json(parameters)?;
self.subtract(params.0).await
}
"multiply" => {
let params: Parameters<ArithmeticParams> = Parameters::from_json(parameters)?;
self.multiply(params.0).await
}
"divide" => {
let params: Parameters<ArithmeticParams> = Parameters::from_json(parameters)?;
self.divide(params.0).await
}
"sqrt" => {
let params: Parameters<AdvancedMathParams> = Parameters::from_json(parameters)?;
self.sqrt(params.0).await
}
"power" => {
let params: Parameters<ArithmeticParams> = Parameters::from_json(parameters)?;
self.power(params.0).await
}
"evaluate" => {
let params: Parameters<ExpressionParams> = Parameters::from_json(parameters)?;
self.evaluate(params.0).await
}
_ => Ok(CallToolResult::error(format!(
"Unknown operation: {}. Supported operations: add, subtract, multiply, divide, sqrt, power, evaluate",
operation
)))
}
}
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"Advanced calculator tool supporting arithmetic operations, square root, power, and expression evaluation"
}
fn parameter_schema(&self) -> JsonValue {
serde_json::json!({
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "multiply", "divide", "sqrt", "power", "evaluate"],
"description": "The mathematical operation to perform"
},
"a": {
"type": "number",
"description": "First number (for arithmetic operations)"
},
"b": {
"type": "number",
"description": "Second number (for arithmetic operations)"
},
"value": {
"type": "number",
"description": "Input value (for single-value operations like sqrt)"
},
"precision": {
"type": "integer",
"description": "Number of decimal places for rounding (optional, default: 2)"
},
"expression": {
"type": "string",
"description": "Mathematical expression to evaluate (for evaluate operation)"
}
},
"required": ["operation"],
"oneOf": [
{
"properties": {
"operation": {"const": "add"},
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["operation", "a", "b"]
},
{
"properties": {
"operation": {"const": "subtract"},
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["operation", "a", "b"]
},
{
"properties": {
"operation": {"const": "multiply"},
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["operation", "a", "b"]
},
{
"properties": {
"operation": {"const": "divide"},
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["operation", "a", "b"]
},
{
"properties": {
"operation": {"const": "sqrt"},
"value": {"type": "number"},
"precision": {"type": "integer"}
},
"required": ["operation", "value"]
},
{
"properties": {
"operation": {"const": "power"},
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["operation", "a", "b"]
},
{
"properties": {
"operation": {"const": "evaluate"},
"expression": {"type": "string"}
},
"required": ["operation", "expression"]
}
]
})
}
fn requires_permission(&self) -> Permission {
Permission::None
}
fn clone_enhanced(&self) -> Box<dyn EnhancedTool> {
Box::new(self.clone())
}
}
impl Default for CalculatorTool {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[tokio::test]
async fn test_calculator_add() {
let calc = CalculatorTool::new();
let params = json!({
"operation": "add",
"a": 5.0,
"b": 3.0
});
let arithmetic_params = ArithmeticParams { a: 5.0, b: 3.0 };
let result = calc.add(arithmetic_params).await.unwrap();
assert!(!result.is_error);
assert_eq!(result.content.len(), 1);
}
#[tokio::test]
async fn test_calculator_divide_by_zero() {
let calc = CalculatorTool::new();
let arithmetic_params = ArithmeticParams { a: 5.0, b: 0.0 };
let result = calc.divide(arithmetic_params).await.unwrap();
assert!(result.is_error);
}
#[tokio::test]
async fn test_calculator_sqrt_negative() {
let calc = CalculatorTool::new();
let math_params = AdvancedMathParams { value: -4.0, precision: None };
let result = calc.sqrt(math_params).await.unwrap();
assert!(result.is_error);
}
#[test]
fn test_simple_expression_evaluation() {
let calc = CalculatorTool::new();
assert_eq!(calc.evaluate_simple_expression("2 + 3"), Some(5.0));
assert_eq!(calc.evaluate_simple_expression("10 - 4"), Some(6.0));
assert_eq!(calc.evaluate_simple_expression("3 * 4"), Some(12.0));
assert_eq!(calc.evaluate_simple_expression("8 / 2"), Some(4.0));
assert_eq!(calc.evaluate_simple_expression("42"), Some(42.0));
}
#[test]
fn test_tool_metadata() {
let calc = CalculatorTool::new();
assert_eq!(calc.name(), "calculator");
assert!(!calc.description().is_empty());
assert_eq!(calc.requires_permission(), Permission::None);
let schema = calc.parameter_schema();
assert!(schema.is_object());
assert!(schema.get("properties").is_some());
}
}