#!/usr/bin/env python3 """ MCP Math Calculator Tool - Gradio Implementation This MCP server provides mathematical calculation capabilities including: - Basic and advanced mathematical operations - Statistical calculations - Expression evaluation - Data analysis functions Supports MCP protocol via Gradio interface. """ import logging import math import os import re import statistics from typing import Any import gradio as gr # Configure logging logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) try: import numpy as np NUMPY_AVAILABLE = True except ImportError: NUMPY_AVAILABLE = False logger.warning("NumPy not available, using basic math operations") class MathCalculator: """Mathematical calculator with advanced functions.""" def __init__(self): """Initialize the math calculator.""" self.allowed_names = { # Basic math functions "abs": abs, "round": round, "min": min, "max": max, "sum": sum, "len": len, # Math module functions "sin": math.sin, "cos": math.cos, "tan": math.tan, "asin": math.asin, "acos": math.acos, "atan": math.atan, "sinh": math.sinh, "cosh": math.cosh, "tanh": math.tanh, "exp": math.exp, "log": math.log, "log10": math.log10, "sqrt": math.sqrt, "pow": pow, "factorial": math.factorial, "floor": math.floor, "ceil": math.ceil, # Constants "pi": math.pi, "e": math.e, # Statistics "mean": statistics.mean, "median": statistics.median, "mode": statistics.mode, "stdev": statistics.stdev, "variance": statistics.variance, } if NUMPY_AVAILABLE: self.allowed_names.update({ "array": np.array, "zeros": np.zeros, "ones": np.ones, "linspace": np.linspace, "arange": np.arange, "dot": np.dot, "cross": np.cross, }) def calculate(self, expression: str) -> str: """ Calculate mathematical expression. Args: expression: Mathematical expression to evaluate Returns: String with calculation results """ try: if not expression.strip(): return "Error: No expression provided" # Check for data analysis operations if self._is_data_operation(expression): return self._handle_data_operation(expression) # Regular mathematical expression result = self._evaluate_expression(expression) # Format result formatted_result = self._format_result(result) response = "🧮 CALCULATION RESULT\n" response += "=" * 30 + "\n" response += f"Expression: {expression}\n" response += f"Result: {formatted_result}\n" # Add additional info for interesting results if isinstance(result, (int, float)): response += self._add_number_info(result) logger.info(f"Successfully calculated: {expression} = {result}") return response except Exception as e: error_msg = f"Calculation error: {e!s}" logger.error(error_msg) return error_msg def _is_data_operation(self, expression: str) -> bool: """Check if expression is a data analysis operation.""" data_keywords = ["mean", "median", "stdev", "variance", "sum", "min", "max"] return any(keyword in expression.lower() for keyword in data_keywords) def _handle_data_operation(self, expression: str) -> str: """Handle data analysis operations.""" try: # Extract numbers from expression numbers = re.findall(r"-?\d+\.?\d*", expression) if not numbers: return "Error: No numbers found for data analysis" data = [float(x) for x in numbers] results = [] results.append("📊 DATA ANALYSIS RESULTS") results.append("=" * 35) results.append(f"Dataset: {data}") results.append(f"Count: {len(data)}") results.append("") # Calculate statistics if len(data) > 0: results.append("📈 STATISTICS:") results.append(f" • Sum: {sum(data):.4f}") results.append(f" • Mean: {statistics.mean(data):.4f}") results.append(f" • Median: {statistics.median(data):.4f}") results.append(f" • Min: {min(data):.4f}") results.append(f" • Max: {max(data):.4f}") results.append(f" • Range: {max(data) - min(data):.4f}") if len(data) > 1: results.append(f" • Std Dev: {statistics.stdev(data):.4f}") results.append(f" • Variance: {statistics.variance(data):.4f}") return "\n".join(results) except Exception as e: return f"Data analysis error: {e!s}" def _evaluate_expression(self, expression: str) -> float | int | str: """Safely evaluate mathematical expression.""" # Clean the expression expression = expression.replace("^", "**") # Replace ^ with ** # Remove potentially dangerous characters/functions dangerous_patterns = ["import", "exec", "eval", "__", "open", "file"] for pattern in dangerous_patterns: if pattern in expression.lower(): raise ValueError(f"Potentially dangerous operation: {pattern}") # Evaluate expression safely try: result = eval(expression, {"__builtins__": {}}, self.allowed_names) return result except NameError as e: raise ValueError(f"Unknown function or variable: {e!s}") except Exception as e: raise ValueError(f"Invalid expression: {e!s}") def _format_result(self, result: Any) -> str: """Format calculation result for display.""" if isinstance(result, (int, float)): if abs(result) < 0.0001 or abs(result) > 1000000: return f"{result:.4e}" return f"{result:.6f}".rstrip("0").rstrip(".") if isinstance(result, complex): return f"{result.real:.4f} + {result.imag:.4f}i" return str(result) def _add_number_info(self, number: float) -> str: """Add interesting information about the number.""" info = [] if isinstance(number, int): if number > 1 and self._is_prime(number): info.append(" ℹ️ This is a prime number!") if number > 0 and math.sqrt(number) == int(math.sqrt(number)): info.append(" ℹ️ This is a perfect square!") if isinstance(number, float): if abs(number - math.pi) < 0.001: info.append(" ℹ️ Very close to π!") elif abs(number - math.e) < 0.001: info.append(" ℹ️ Very close to e!") return "\n" + "\n".join(info) if info else "" def _is_prime(self, n: int) -> bool: """Check if a number is prime.""" if n < 2: return False for i in range(2, int(math.sqrt(n)) + 1): if n % i == 0: return False return True # Initialize the math calculator math_calculator = MathCalculator() def calculate_math_mcp(expression: str) -> str: """ MCP-compatible mathematical calculation function. Args: expression: Mathematical expression to calculate Returns: String with formatted calculation results """ try: if not expression.strip(): return "Error: No expression provided" result = math_calculator.calculate(expression) return result except Exception as e: error_msg = f"Error calculating expression: {e!s}" logger.error(error_msg) return error_msg def create_gradio_interface(): """Create and configure the Gradio interface.""" interface = gr.Interface( fn=calculate_math_mcp, inputs=[ gr.Textbox( label="Mathematical Expression", placeholder="Enter expression (e.g., 2^3 + sqrt(16) or mean([1,2,3,4,5]))", lines=2 ) ], outputs=[ gr.Textbox( label="Calculation Result", lines=15, show_copy_button=True ) ], title="🧮 MCP Math Calculator", description=""" **Mathematical Calculation MCP Server** Perform various calculations: - Basic arithmetic (+ - * / ^ %) - Advanced functions (sin, cos, tan, log, sqrt, etc.) - Statistical analysis (mean, median, stdev, etc.) - Data operations on lists of numbers Examples: `2^3 + sqrt(16)`, `sin(pi/2)`, `mean([1,2,3,4,5])` """, examples=[ ["2^3 + sqrt(16)"], ["sin(pi/2) + cos(0)"], ["mean([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])"], ["factorial(5) / (2^3)"], ["log(e^2) + sqrt(25)"] ], allow_flagging="never", analytics_enabled=False ) return interface def main(): """Main function to run the Gradio app.""" port = int(os.getenv("GRADIO_SERVER_PORT", 7860)) host = os.getenv("GRADIO_SERVER_NAME", "0.0.0.0") logger.info(f"Starting MCP Math Calculator on {host}:{port}") interface = create_gradio_interface() interface.launch( server_name=host, server_port=port, share=False, debug=False, quiet=False, show_error=True ) if __name__ == "__main__": main()