#
tokens: 2682/50000 10/10 files
lines: off (toggle) GitHub
raw markdown copy
# Directory Structure

```
├── .github
│   └── workflows
│       └── CI.yml
├── .gitignore
├── .python-version
├── Dockerfile
├── LICENSE
├── pyproject.toml
├── README.md
├── src
│   └── mcp_server_calculator
│       ├── __init__.py
│       ├── __main__.py
│       └── calculator.py
├── tests
│   └── test_calculator.py
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
3.10

```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info

# Virtual environments
.venv

```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
# Calculator MCP Server

A Model Context Protocol server for calculating. This server enables LLMs to use calculator for precise numerical calculations.

### Available Tools

- `calculate` - Calculates/evaluates the given expression.
  - `expression` (string, required): Expression to be calculated

## Installation

### Using uv (recommended)

When using [`uv`](https://docs.astral.sh/uv/) no specific installation is needed. We will
use [`uvx`](https://docs.astral.sh/uv/guides/tools/) to directly run *mcp-server-calculator*.

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

### Using PIP

Alternatively you can install `mcp-server-calculator` via pip:

```bash
pip install mcp-server-calculator
```

After installation, you can run it as a script using:

```bash
python -m mcp_server_calculator
```

## Configuration

### Using uv (recommended)

Add this to your MCP client settings:

```json
"mcpServers": {
  "calculator": {
    "command": "uvx",
    "args": ["mcp-server-calculator"]
  }
}
```

### Using PIP

Alternatively add this to your MCP client settings:

```json
"mcpServers": {
  "calculator": {
    "command": "python",
    "args": ["-m", "mcp_server_calculator"]
  }
}
```

## License

mcp-server-calculator is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License. For more details, please see the LICENSE file in the project repository.

```

--------------------------------------------------------------------------------
/src/mcp_server_calculator/__main__.py:
--------------------------------------------------------------------------------

```python
from mcp_server_calculator import main

main()

```

--------------------------------------------------------------------------------
/src/mcp_server_calculator/__init__.py:
--------------------------------------------------------------------------------

```python
from .calculator import main

if __name__ == "__main__":
    main()

```

--------------------------------------------------------------------------------
/.github/workflows/CI.yml:
--------------------------------------------------------------------------------

```yaml
name: CI

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v4

    - name: Set up Python
      uses: actions/setup-python@v5
      with:
        python-version-file: .python-version

    - name: Install uv
      uses: astral-sh/setup-uv@v3

    - name: Install dependencies
      run: uv sync

    - name: Build package
      run: uv build

    - name: Run tests
      run: uv run python -m unittest discover -s tests

```

--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------

```dockerfile
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS uv

WORKDIR /app

ENV UV_COMPILE_BYTECODE=1

ENV UV_LINK_MODE=copy

RUN --mount=type=cache,target=/root/.cache/uv \
    --mount=type=bind,source=uv.lock,target=uv.lock \
    --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
    uv sync --frozen --no-install-project --no-dev --no-editable

ADD . /app
RUN --mount=type=cache,target=/root/.cache/uv \
    uv sync --frozen --no-dev --no-editable

FROM python:3.12-slim-bookworm

WORKDIR /app
 
COPY --from=uv /root/.local /root/.local
COPY --from=uv --chown=app:app /app/.venv /app/.venv

ENV PATH="/app/.venv/bin:$PATH"

ENTRYPOINT ["mcp-server-calculator"]

```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
[project]
name = "mcp-server-calculator"
version = "0.2.0"
description = "A Model Context Protocol server for calculating"
readme = "README.md"
requires-python = ">=3.10"
authors = [{ name = "He Jie", email = "[email protected]" }]
keywords = ["mcp", "llm", "math", "calculator"]
license = { text = "MIT" }
urls = { Source = "https://github.com/githejie/mcp-server-calculator" }
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
]
dependencies = [
    "mcp>=1.4.1",
]

[project.scripts]
mcp-server-calculator = "mcp_server_calculator:main"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

```

--------------------------------------------------------------------------------
/src/mcp_server_calculator/calculator.py:
--------------------------------------------------------------------------------

```python
import ast
import operator
import math
from mcp.server.fastmcp import FastMCP

def evaluate(expression: str) -> str:
    allowed_operators = {
        ast.Add: operator.add,
        ast.Sub: operator.sub,
        ast.Mult: operator.mul,
        ast.Div: operator.truediv,
        ast.FloorDiv: operator.floordiv,
        ast.Mod: operator.mod,
        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)
            if type(node.op) in allowed_operators:
                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('÷', '/')
    parsed_expr = ast.parse(expression, mode='eval')
    result = eval_expr(parsed_expr.body)
    return str(result)

mcp = FastMCP("calculator")

@mcp.tool()
async def calculate(expression: str) -> str:
    """Calculates/evaluates the given expression."""
    return evaluate(expression)

def main():
    mcp.run()

```

--------------------------------------------------------------------------------
/tests/test_calculator.py:
--------------------------------------------------------------------------------

```python
import math
import unittest
from src.mcp_server_calculator.calculator import evaluate

class TestCalculator(unittest.TestCase):
    def test_addition(self):
        self.assertEqual(evaluate("1 + 1"), "2")

    def test_subtraction(self):
        self.assertEqual(evaluate("5 - 3"), "2")

    def test_multiplication(self):
        self.assertEqual(evaluate("2 * 3"), "6")
        self.assertEqual(evaluate("2 × 3"), "6")

    def test_division(self):
        self.assertEqual(evaluate("8 / 2"), "4.0")
        self.assertEqual(evaluate("8 ÷ 2"), "4.0")

    def test_floor_division(self):
        self.assertEqual(evaluate("7 // 2"), "3")

    def test_modulus(self):
        self.assertEqual(evaluate("10 % 3"), "1")

    def test_power(self):
        self.assertEqual(evaluate("2 ** 3"), "8")
        self.assertEqual(evaluate("2 ^ 3"), "8")

    def test_unary_minus(self):
        self.assertEqual(evaluate("-5"), "-5")

    def test_complex_expression(self):
        self.assertEqual(evaluate("2 + 3 * (4 - 1) / 2 ** 2"), "4.25")

    def test_parentheses_expression(self):
        self.assertEqual(evaluate("(2 + 3) * 4"), "20")

    def test_negative_numbers(self):
        self.assertEqual(evaluate("-2 + 3"), "1")
        self.assertEqual(evaluate("4 * -2"), "-8")
        self.assertEqual(evaluate("-6 / 2"), "-3.0")

    def test_floating_point_operations(self):
        self.assertEqual(evaluate("0.5 + 0.25"), "0.75")
        self.assertEqual(evaluate("2.5 * 2"), "5.0")
        self.assertEqual(evaluate("5.0 / 2"), "2.5")

    def test_large_numbers(self):
        self.assertEqual(evaluate("123456789 * 987654321"), str(123456789 * 987654321))

    def test_floating_point_precision(self):
        self.assertAlmostEqual(float(evaluate("0.1 + 0.2")), 0.3, places=7)

    def test_unsupported_operation(self):
        with self.assertRaises(ValueError):
            evaluate("unknown")

    def test_empty_string(self):
        with self.assertRaises(SyntaxError):
            evaluate("")

    def test_whitespace_string(self):
        with self.assertRaises(SyntaxError):
            evaluate("   ")

    def test_invalid_expression(self):
        with self.assertRaises(SyntaxError):
            evaluate("2 +")

    def test_division_by_zero(self):
        with self.assertRaises(ZeroDivisionError):
            evaluate("1 / 0")

    def test_floor_division_by_zero(self):
        with self.assertRaises(ZeroDivisionError):
            evaluate("1 // 0")

    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()

```