Skip to main content
Glama
daffamumtaz2361

safe-mathjs-mcp

README.md
# safe-mathjs-mcp

A sandboxed math [MCP](https://modelcontextprotocol.io) server for Node.js. It gives LLM agents a safe place to evaluate, simplify, and differentiate mathematical expressions — powered by [mathjs](https://mathjs.org).

**Why "safe"?** Untrusted model input is evaluated inside a worker thread against a strict AST allowlist: only pure functions over numbers and number-matrices, no `eval`, no string processing, no assignments, no accessors, no units, no randomness — with a hard execution timeout.

## Tools

| Tool | Description |
|---|---|
| `evaluate` | Evaluate a numeric expression. Supports variables and configurable precision. |
| `simplify` | Symbolic simplification (collect like terms, fold constants). Free symbols stay symbolic. |
| `derivative` | Symbolic differentiation with respect to a variable. |

## Quick start

Requires Node.js >= 18.

```sh
npm install
npm start            # run the stdio server directly
npm run inspect      # interactive testing via the MCP inspector
```

To connect the server to an agent harness (Claude Desktop, Claude Code, Zed, VS Code, ...), see [Installing in agent harnesses](#installing-in-agent-harnesses).

## Installing in agent harnesses

The server speaks MCP over stdio, so every harness works the same way: it spawns `node` with the entry script. Prerequisites: `npm install` has been run in the repo, Node.js >= 18 is on `PATH`, and you use an **absolute** path to the repo (replace `/path/to/safe-mathjs-mcp` below). The working directory doesn't matter — the worker script and mathjs resolve relative to the entry file.

The universal entry, reused below in each harness's format:

```json
{
  "mcpServers": {
    "safe-mathjs": {
      "command": "node",
      "args": ["/path/to/safe-mathjs-mcp/src/index.js"]
    }
  }
}
```

### Claude Desktop

Edit the config file — `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS), `%APPDATA%\Claude\claude_desktop_config.json` (Windows), or `~/.config/Claude/claude_desktop_config.json` (Linux) — and add the universal `mcpServers` entry from above. **Restart Claude Desktop** afterwards.

### Claude Code

Add it from the CLI (no config file needed):

```sh
claude mcp add safe-mathjs -- node /path/to/safe-mathjs-mcp/src/index.js
claude mcp list          # verify
```

By default this applies to your user account; use `--scope project` to scope it to the current project or `--scope local` for your local machine only. Alternatively, commit a `.mcp.json` in the project root with the same `mcpServers` shape.

### Zed

Add an `mcp` key (note: Zed uses `mcp`, not `mcpServers`) to `~/.config/zed/settings.json`:

```json
{
  "mcp": {
    "safe-mathjs": {
      "command": "node",
      "args": ["/path/to/safe-mathjs-mcp/src/index.js"],
      "enabled": true
    }
  }
}
```

Zed also accepts an `environment` object here if you want to pass `CALC_TIMEOUT_MS`.

### VS Code (Copilot)

Create `.vscode/mcp.json` in your workspace. **VS Code uses a `servers` key and an optional `type` field**, which differs from most other harnesses:

```json
{
  "servers": {
    "safe-mathjs": {
      "type": "stdio",
      "command": "node",
      "args": ["/path/to/safe-mathjs-mcp/src/index.js"]
    }
  }
}
```

Reload the window after adding it.

### Cursor

Create `.cursor/mcp.json` in the project root with the same `mcpServers` shape as the Claude Desktop example above.

### Cline

Add it through Cline's MCP settings (`cline_mcp_settings.json`, reachable from the Cline settings UI) — same `mcpServers` shape as Claude Desktop. Cline lets you set environment variables per server in the same JSON.

### Any MCP SDK client

```js
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

const transport = new StdioClientTransport({
  command: "node",
  args: ["/path/to/safe-mathjs-mcp/src/index.js"],
});
const client = new Client({ name: "my-app", version: "1.0.0" });
await client.connect(transport);

const { content } = await client.callTool({
  name: "evaluate",
  arguments: { expression: "2^10" },
});
```

### Troubleshooting

- **Restart the harness** after editing config files — most clients only load MCP servers at startup.
- **Wrong path / node not found** — use the absolute repo path, and make sure `node` resolves for the harness's shell (check with `node --version`).
- **Passing env vars** — harnesses that support an `environment` field (Zed, Cline) can set `CALC_TIMEOUT_MS` there. For clients that don't (e.g. Claude Desktop), wrap the command: `env CALC_TIMEOUT_MS=5000 node /path/to/safe-mathjs-mcp/src/index.js`.
- **Sanity check first** — run `npm run inspect` in the repo to confirm the server starts and the tools respond before wiring it into a harness.

## Tool reference

### `evaluate`

- `expression` (required) — math expression, max 512 chars. Example: `2 * (12 + sqrt(255))^2`
- `precision` (optional) — significant digits for the result, 1–100 (default 10)
- `variables` (optional) — named numeric values, e.g. `{ x: 2 }`

```text
evaluate("5! + mean([1,2,3]) + det([[1,2],[3,4]])")   → 120
evaluate("x^2 + 1", { variables: { x: 3 } })           → 10
evaluate("1/3", { precision: 30 })                     → 0.333333333333333333333333333333
```

### `simplify`

- `expression` (required)
- `variables` (optional) — known values folded in as constants

```text
simplify("3*x + 2*x")       → 5 * x
simplify("x/x")             → 1
simplify("x^2 + 2*x + 1")   → x ^ 2 + 2 * x + 1
```

Note: simplification is heuristic — it collects like terms and folds constants but does not expand products or factor polynomials. Free symbols are treated as unknowns; e.g. `x/x` simplifies to `1`, dropping the `x != 0` case.

### `derivative`

- `expression` (required)
- `variable` (required) — variable to differentiate with respect to, e.g. `x`

```text
derivative("x^3 + sin(x)", "x")   → 3 * x ^ 2 + cos(x)
derivative("a*x^2 + b", "x")      → 2 * a * x
derivative("x^2", "y")            → 0
```

Other symbols in the expression are treated as free constants; the result is simplified.

## Supported surface

**Operators:** `+ - * / ^ % !` and unary plus/minus

**Constants:** `pi`, `e`, `tau`

**Functions** (pure math over numbers and number-matrices):

| Category | Functions |
|---|---|
| Arithmetic & roots | `sqrt`, `cbrt`, `abs`, `pow`, `exp`, `log`, `ln`, `log10`, `log2`, `nthRoot`, `gcd`, `lcm`, `factorial`, `sign`, `hypot`, `mod` |
| Trigonometry | `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`, `sinh`, `cosh`, `tanh` |
| Rounding & min/max | `floor`, `ceil`, `round`, `min`, `max` |
| Number theory | `isPrime`, `combinations`, `permutations` |
| Statistics | `mean`, `median`, `std`, `sum`, `prod`, `variance`, `mode` |
| Linear algebra | `det`, `inv`, `transpose`, `norm`, `dot`, `cross` |

**Array literals** like `[1,2,3]` and matrices like `[[1,2],[3,4]]` are supported for statistics and linear algebra.

Note: elementwise application of scalar functions to matrices (e.g. `sqrt([4,9])`) is not supported — write `[sqrt(4), sqrt(9)]` instead.

## Security model

Expressions are untrusted model input, so evaluation happens in a dedicated worker thread behind layered guards:

1. **Worker isolation** — evaluation runs in a worker thread; if an expression ever hangs, the thread is terminated (2 s timeout, configurable via `CALC_TIMEOUT_MS`). If the worker crashes, the next call respawns it.
2. **AST allowlist, not blocklist** — the expression is parsed and every node validated before evaluation. Only these node types pass:
   - `ConstantNode` (numbers only — string/boolean/null literals are rejected)
   - `SymbolNode` (`pi`, `e`, `tau`, or declared variables)
   - `OperatorNode` (allowlisted operators)
   - `FunctionNode` (allowlisted functions)
   - `ParenthesisNode`, `ArrayNode`
3. **No code execution** — mathjs is a pure AST interpreter; `eval`/`new Function` are never used, and the string-processing functions (`evaluate`, `parse`, `compile`, `format`, `print`) are not in the allowlist.
4. **Structural exclusions** — assignments (`x = 5`), object literals, indexing (`A[1]`), conditionals (`a ? b : c`), ranges (`1:5`), multi-statement blocks, comparison/logical/bitwise operators, units, and randomness are all rejected with a `Disallowed ...` error.
5. **Result checks** — complex numbers (scalar or inside arrays) and NaN are rejected after evaluation.
6. **Bounded input** — expressions are capped at 512 characters; variables must be valid identifiers and prototype-polluting names (`__proto__`, `constructor`, `prototype`) are rejected.
7. **BigNumber precision** — all math runs on BigNumber with configurable significant digits (default 10), avoiding float artifacts.

Anything outside this surface fails loudly with a descriptive error — nothing is silently coerced.

## Configuration

| Env var | Default | Purpose |
|---|---|---|
| `CALC_TIMEOUT_MS` | `2000` | Max wall-clock time for a single evaluation before the worker is terminated |

## Project layout

```
src/
  index.js              MCP server: tool registration, worker lifecycle, timeouts
  evaluator-worker.js   Sandbox: parsing, AST validation, whitelists, evaluation
```

## Extending the whitelist

The allowlists live at the top of `src/evaluator-worker.js` (`ALLOWED_FUNCTIONS`, `ALLOWED_OPERATORS`, `ALLOWED_SYMBOLS`, `ALLOWED_NODES`). When adding a function, keep to the rule: *pure, deterministic, numbers and number-matrices only*. Functions that take strings, return units, or accept function-valued arguments (e.g. `map`, `format`, `unit`) do not fit the model. Verify the function exists in the installed mathjs version before adding (e.g. `solve` and `nextPrime` were removed in mathjs 13).

## License

MIT

TDQS

A4.1/5.0

Scored across 3 tools

Disambiguation5/5

Each tool has a clearly distinct purpose: evaluate computes numeric results, simplify performs algebraic simplification, derivative computes symbolic derivatives. There is no overlap or ambiguity between them.

Naming Consistency5/5

All tool names are single, lowercase imperative-style verbs (evaluate, simplify, derivative) that accurately describe their actions. The naming pattern is perfectly consistent.

Tool Count5/5

Three tools is an appropriate, well-scoped size for a focused math evaluation server. Each tool provides a meaningful core capability without unnecessary bloat.

Completeness4/5

The set covers evaluation, simplification, and differentiation, which are the most common symbolic math operations. It lacks symbolic integration, equation solving, or advanced algebraic manipulation, but these are reasonable gaps for a 'safe' math server.

Maintenance

ActivityMaintained
ResponsivenessNo issues