An MCP server that gives LLM agents exact, arbitrary-precision, and symbolic math. Backed by SymPy and mpmath, so 1/3 + 1/7 returns 10/21 — not 0.47619047619047616.
Every tool returns a typed Pydantic model, so MCP clients see structured output with a generated JSON schema — no re-parsing. Docstrings lead with USE THIS WHEN … to steer LLM tool routing.
37 tools across seven domains:
| Domain | Tools |
|---|---|
| Arithmetic / numeric | evaluate, evaluate_batch, numeric (up to 10,000 digits) |
| Algebra | simplify, expand, factor, solve_equation, solve_inequality, solve_system, polynomial_roots, nroots |
| Calculus | differentiate, integrate (definite & indefinite), limit, series, summation |
| Number theory | gcd, lcm, factorint, is_prime, nth_prime, next_prime, mod_pow, mod_inverse |
| Combinatorics | binomial, permutations, combinations |
| Linear algebra | matrix_determinant, matrix_inverse, matrix_multiply, matrix_eigenvalues, matrix_solve |
| Conversions / stats | stats, to_rational, to_base, from_base, convert_units |
- No
eval. All inputs are parsed through SymPy's AST-based expression parser. Implicit multiplication is supported (2xparses as2*x). - Input caps (see
src/math_mcp/limits.py) reject pathological inputs before they reach SymPy: max 4096-char expressions, max 4096-bit integers, max 32×32 matrices, max 10,000 numeric digits, max 50-order series, max 20-order derivatives. - Typed error messages identify which limit was exceeded so callers can adjust.
uvx --from git+https://github.com/Jsewill/math-mcp sympy-math-mcpor from a local checkout:
uv tool install .
sympy-math-mcppip install git+https://github.com/Jsewill/math-mcp
sympy-math-mcpThe PyPI distribution is named
sympy-math-mcp(the baremath-mcpname belongs to an unrelated package). Once published,uvx sympy-math-mcp/pip install sympy-math-mcpwill work without the git URL.
claude mcp add math-mcp --scope user -- uvx --from git+https://github.com/Jsewill/math-mcp sympy-math-mcpOr add to ~/.claude.json manually:
{
"mcpServers": {
"math-mcp": {
"command": "uvx",
"args": ["--from", "git+https://github.com/Jsewill/math-mcp", "sympy-math-mcp"]
}
}
}Edit ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows):
{
"mcpServers": {
"math-mcp": {
"command": "uvx",
"args": ["--from", "git+https://github.com/Jsewill/math-mcp", "sympy-math-mcp"]
}
}
}The MCP server ships in-context routing instructions, but those are persuasion — a stubborn agent can still shell out to python -c, bc, or $((...)). The plugin/ directory adds runtime enforcement:
- PreToolUse:Bash hook — regex-detects shell calculator patterns (
python -c,bc,dc,qalc,$((...)),node -e,expr,perl -e,awk 'BEGIN{print}') and nudges the model towardmcp__math-mcp__evaluate/evaluate_batch. Catches the "I'll just use bash" path. - UserPromptSubmit hook — when the user's prompt contains math signals (digits with operators, exponents,
calculate/solve/integrate/ …, percent-of, unit conversions, primality, stats, base conversions, matrix ops), injects a pre-response directive into context for that turn only requiring any derived number to come from amcp__math-mcp__*call. Zero context cost on non-math turns. - SubagentStart hook — subagents don't inherit the parent session's
UserPromptSubmitinjection, so without this hook delegation (to Explore / Plan / custom agents) is an enforcement hole. Re-plants a short routing directive into every spawned subagent. - Skill (
math-mcp) — phrase-triggered tool picker; loads only when it's needed. - Slash command (
/math-verify) — post-hoc re-verification of every derived number in the last assistant turn.
Session-level routing rules arrive via the server's MCP instructions field, so no SessionStart hook is needed — it would just duplicate what the MCP server already ships.
Honest limit: no Claude Code hook fires on the assistant's prose output itself, so mental arithmetic that is embedded directly in text (no tool call, no shell fallback) cannot be blocked at inference time. The above hooks maximize pressure at every boundary the harness exposes (user prompt, tool call); the rest is the model's training-level reflex for tool use.
The detector is shared (Node ESM in plugin/core/detect_math.mjs; inlined into plugin/opencode/math-mcp.ts so the opencode install stays one file).
From this repo as a marketplace (recommended):
/plugin marketplace add Jsewill/math-mcp
/plugin install math-mcp@math-mcp
Or point Claude Code directly at the plugin directory:
claude plugin install /path/to/math-mcp/pluginThe plugin's manifest (plugin/.claude-plugin/plugin.json) also registers the MCP server, so installing the plugin replaces the claude mcp add step above.
Drop the single-file plugin into your opencode plugins dir and add math-mcp under mcp in your opencode.json:
# global (every opencode project)
cp plugin/opencode/math-mcp.ts ~/.config/opencode/plugins/math-mcp.ts
# or per-project
cp plugin/opencode/math-mcp.ts .opencode/plugins/math-mcp.tsMerge plugin/opencode/opencode.json into your config to register the MCP server. Optionally copy plugin/opencode/AGENTS.md into project root for session-level routing rules.
evaluate(expression="2**100 + 1")
→ exact: "1267650600228229401496703205377"
evaluate(expression="1/3 + 1/7 + 1/11")
→ exact: "131/231"
numeric(expression="pi", digits=60)
→ exact: "3.14159265358979323846264338327950288419716939937510582097494"
solve_equation(equation="x**2 - 2 = 0", domain="real")
→ solutions: ["-sqrt(2)", "sqrt(2)"]
solve_inequality(inequality="x**2 - 4 > 0", variable="x")
→ solution_set: "Union(Interval.open(-oo, -2), Interval.open(2, oo))"
polynomial_roots(polynomial="(x - 1)**2 * (x - 2)")
→ multiplicities: {"1": 2, "2": 1}
integrate(expression="1/(1+x**2)", variable="x", lower="0", upper="1")
→ exact: "pi/4" (decimal: 0.7853981633974483…)
summation(expression="1/n**2", index="n", lower="1", upper="oo")
→ exact: "pi**2/6"
is_prime(number="2**521 - 1")
→ value: true (Mersenne M_521)
mod_pow(base="7", exponent="2**100", modulus="10**9+7")
→ value: 641087921
binomial(n=52, k=5)
→ value: 2598960 (poker hands)
to_base(number="255", base=16)
→ digits: "ff"
convert_units(value="5", source_unit="meter", target_unit="foot")
→ converted: "6250/381 foot" (decimal: 16.4041994750656)
git clone https://github.com/Jsewill/math-mcp
cd math-mcp
uv sync --all-groups
uv run coverage run -m pytest
uv run coverage reportCoverage is enforced at 100% (fail_under = 100 in pyproject.toml); any regression breaks the test run.
Run the server directly (stdio transport):
uv run sympy-math-mcpMIT — see LICENSE.