BasalGanglia's picture
🛠️ Fix HuggingFace Space configuration - Remove quotes from frontmatter
64ced8b verified
#!/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()