|
|
|
|
|
""" |
|
|
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 |
|
|
|
|
|
|
|
|
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 = { |
|
|
|
|
|
"abs": abs, "round": round, "min": min, "max": max, |
|
|
"sum": sum, "len": len, |
|
|
|
|
|
|
|
|
"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, |
|
|
|
|
|
|
|
|
"pi": math.pi, "e": math.e, |
|
|
|
|
|
|
|
|
"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" |
|
|
|
|
|
|
|
|
if self._is_data_operation(expression): |
|
|
return self._handle_data_operation(expression) |
|
|
|
|
|
|
|
|
result = self._evaluate_expression(expression) |
|
|
|
|
|
|
|
|
formatted_result = self._format_result(result) |
|
|
|
|
|
response = "🧮 CALCULATION RESULT\n" |
|
|
response += "=" * 30 + "\n" |
|
|
response += f"Expression: {expression}\n" |
|
|
response += f"Result: {formatted_result}\n" |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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("") |
|
|
|
|
|
|
|
|
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.""" |
|
|
|
|
|
expression = expression.replace("^", "**") |
|
|
|
|
|
|
|
|
dangerous_patterns = ["import", "exec", "eval", "__", "open", "file"] |
|
|
for pattern in dangerous_patterns: |
|
|
if pattern in expression.lower(): |
|
|
raise ValueError(f"Potentially dangerous operation: {pattern}") |
|
|
|
|
|
|
|
|
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 |
|
|
|
|
|
|
|
|
|
|
|
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() |
|
|
|