From 4cd1bfcef2e4ed79e7acb285028a6d3f712db99f Mon Sep 17 00:00:00 2001 From: githejie Date: Sat, 10 May 2025 10:19:56 +0800 Subject: [PATCH] Support the python math library --- pyproject.toml | 2 +- src/mcp_server_calculator/calculator.py | 18 ++++++++++++++++++ tests/test_calculator.py | 16 ++++++++++++++++ uv.lock | 2 +- 4 files changed, 36 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fab912c..9862d28 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "mcp-server-calculator" -version = "0.1.1" +version = "0.2.0" description = "A Model Context Protocol server for calculating" readme = "README.md" requires-python = ">=3.10" diff --git a/src/mcp_server_calculator/calculator.py b/src/mcp_server_calculator/calculator.py index be97f8e..56839cd 100644 --- a/src/mcp_server_calculator/calculator.py +++ b/src/mcp_server_calculator/calculator.py @@ -1,5 +1,6 @@ import ast import operator +import math from mcp.server.fastmcp import FastMCP def evaluate(expression: str) -> str: @@ -13,10 +14,23 @@ def evaluate(expression: str) -> str: ast.Pow: operator.pow, ast.USub: operator.neg, } + allowed_names = { + k: getattr(math, k) + for k in dir(math) + if not k.startswith("__") + } + allowed_names.update({ + "pi": math.pi, + "e": math.e, + }) def eval_expr(node): if isinstance(node, ast.Constant): return node.value + elif isinstance(node, ast.Name): + if node.id in allowed_names: + return allowed_names[node.id] + raise ValueError(f"Unknown identifier: {node.id}") elif isinstance(node, ast.BinOp): left = eval_expr(node.left) right = eval_expr(node.right) @@ -24,6 +38,10 @@ def eval_expr(node): return allowed_operators[type(node.op)](left, right) elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub): return -eval_expr(node.operand) + elif isinstance(node, ast.Call): + func = eval_expr(node.func) + args = [eval_expr(arg) for arg in node.args] + return func(*args) raise ValueError(f"Unsupported operation: {ast.dump(node)}") expression = expression.replace('^', '**').replace('×', '*').replace('÷', '/') diff --git a/tests/test_calculator.py b/tests/test_calculator.py index 08dcb2b..481ffc0 100644 --- a/tests/test_calculator.py +++ b/tests/test_calculator.py @@ -1,3 +1,4 @@ +import math import unittest from src.mcp_server_calculator.calculator import evaluate @@ -79,5 +80,20 @@ def test_modulus_by_zero(self): with self.assertRaises(ZeroDivisionError): evaluate("1 % 0") + def test_math_functions(self): + self.assertAlmostEqual(float(evaluate("sin(pi/2)")), math.sin(math.pi/2), places=7) + self.assertAlmostEqual(float(evaluate("cos(0)")), math.cos(0), places=7) + self.assertAlmostEqual(float(evaluate("sqrt(16)")), math.sqrt(16), places=7) + self.assertAlmostEqual(float(evaluate("log(e)")), math.log(math.e), places=7) + self.assertAlmostEqual(float(evaluate("exp(1)")), math.exp(1), places=7) + self.assertAlmostEqual(float(evaluate("tan(0)")), math.tan(0), places=7) + self.assertAlmostEqual(float(evaluate("fabs(-5)")), math.fabs(-5), places=7) + self.assertAlmostEqual(float(evaluate("factorial(5)")), math.factorial(5), places=7) + self.assertAlmostEqual(float(evaluate("pow(2, 5)")), math.pow(2, 5), places=7) + self.assertAlmostEqual(float(evaluate("degrees(pi)")), math.degrees(math.pi), places=7) + self.assertAlmostEqual(float(evaluate("radians(180)")), math.radians(180), places=7) + self.assertAlmostEqual(float(evaluate("pi")), math.pi, places=7) + self.assertAlmostEqual(float(evaluate("e")), math.e, places=7) + if __name__ == '__main__': unittest.main() diff --git a/uv.lock b/uv.lock index 4edae81..7da6b56 100644 --- a/uv.lock +++ b/uv.lock @@ -141,7 +141,7 @@ wheels = [ [[package]] name = "mcp-server-calculator" -version = "0.1.1" +version = "0.2.0" source = { editable = "." } dependencies = [ { name = "mcp" },