{
  "markdown": "# SageMath MCP Server\n\n<!-- mcp-name: io.github.XBP-Europe/sagemath-mcp -->\n\n[![CI](https://github.com/XBP-Europe/sagemath-mcp/actions/workflows/ci.yml/badge.svg)](https://github.com/XBP-Europe/sagemath-mcp/actions/workflows/ci.yml)\n[![Release](https://img.shields.io/github/v/release/XBP-Europe/sagemath-mcp.svg)](https://github.com/XBP-Europe/sagemath-mcp/releases/latest)\n[![PyPI](https://img.shields.io/pypi/v/sagemath-mcp.svg)](https://pypi.org/project/sagemath-mcp/)\n[![GHCR](https://img.shields.io/badge/GHCR-sagemath--mcp-blue?logo=github)](https://github.com/XBP-Europe/sagemath-mcp/pkgs/container/sagemath-mcp)\n[![License](https://img.shields.io/github/license/XBP-Europe/sagemath-mcp.svg)](LICENSE)\n[![Python](https://img.shields.io/badge/python-3.12%2B-blue.svg)](https://www.python.org/downloads/)\n[![MCP](https://img.shields.io/badge/MCP-Model%20Context%20Protocol-purple)](https://modelcontextprotocol.io/)\n[![FastMCP](https://img.shields.io/badge/FastMCP-3.4%2B-green.svg)](https://gofastmcp.com/)\n[![SageMath](https://img.shields.io/badge/SageMath-10.9-orange)](https://www.sagemath.org/)\n[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://docs.astral.sh/ruff/)\n[![Typed](https://img.shields.io/badge/type--checked-py.typed-blue)](https://peps.python.org/pep-0561/)\n[![Coverage](https://img.shields.io/badge/coverage-100%25-brightgreen.svg)](https://github.com/XBP-Europe/sagemath-mcp/actions/workflows/ci.yml)\n[![Downloads](https://img.shields.io/pypi/dm/sagemath-mcp.svg)](https://pypi.org/project/sagemath-mcp/)\n[![MCP Registry](https://img.shields.io/badge/MCP%20Registry-listed-purple)](https://registry.modelcontextprotocol.io/)\n[![Signed](https://img.shields.io/badge/images-cosign%20signed-blueviolet?logo=sigstore)](https://github.com/XBP-Europe/sagemath-mcp/blob/main/.github/workflows/release.yml)\n[![Dependabot](https://img.shields.io/badge/dependabot-enabled-025E8C?logo=dependabot)](https://github.com/XBP-Europe/sagemath-mcp/blob/main/.github/dependabot.yml)\n[![Last commit](https://img.shields.io/github/last-commit/XBP-Europe/sagemath-mcp.svg)](https://github.com/XBP-Europe/sagemath-mcp/commits/main)\n\nA [Model Context Protocol](https://modelcontextprotocol.io/) server that gives an\nLLM a sandboxed mathematical subset of [SageMath](https://www.sagemath.org/) —\nsymbolic calculus, number theory, linear algebra, ODEs, plotting, combinatorics,\ngraphs, groups, elliptic curves, and more. Each MCP session gets a dedicated Sage\nworker process, so variables, functions, and assumptions **persist across tool\ncalls**. It ships **40 MCP tools**, one of which — `verify_claim` — re-checks a\nstated result through a proof ladder and answers `proved` / `refuted` /\n`supported` / `undecided` with its evidence.\n\nCaller code is **deny-by-default**: the full breadth of Sage mathematics is\nreachable, but imports, the external CAS interfaces, and the file / display /\npersistence primitives are not. The policy accepts **98.6% of SageMath's own\n432,878 documented doctest examples** while refusing the rest — measured on every\nCI run (see [Security](#security)).\n\nFull manual: **[USAGE.md](USAGE.md)** — every tool's parameters and examples, how\ncode is interpreted, and the security model in depth.\n\n## Install & run\n\n**Recommended — the container image (SageMath is baked in):**\n\n```bash\ndocker run --rm \\\n  --read-only --tmpfs /tmp:rw,size=512m --tmpfs /home/sage/.sage:rw,size=256m \\\n  --cap-drop ALL --security-opt no-new-privileges --pids-limit 256 --memory 4g \\\n  -p 127.0.0.1:8314:8314 \\\n  ghcr.io/xbp-europe/sagemath-mcp:latest\n```\n\nThose flags are the hardening the server expects; the port is published on\nloopback deliberately — the server executes code and authenticates nobody.\n`docker compose up --build` applies the same hardening from one reviewed file.\nReleased images are signed with Cosign.\n\n**From PyPI (bring your own Sage runtime):**\n\n```bash\npip install sagemath-mcp\nsagemath-mcp                                             # stdio (default)\nsagemath-mcp --transport streamable-http --port 8314    # HTTP on 127.0.0.1\n```\n\nThis needs a working SageMath on the host — either `sage` on your `PATH` or the\n`sagemath/sagemath` Docker image.\n\n**A Sage runtime without the 3 GB image ([passagemath](https://github.com/passagemath/passagemath), optional):**\n\n```bash\npip install \"sagemath-mcp[passagemath]\"    # ~1 GB, no Docker, no local Sage build\nsagemath-mcp\n```\n\nA pip-installable, modularized fork of SageMath. `from sage.all import *` and the\nworker run unmodified; the server detects the runtime at import and loads the\nmatching security artifacts, so the deny-by-default policy is equivalent on both.\nIt is pinned exactly (`passagemath-standard==10.8.9`) and exercised by its own CI\nlane — the whole suite plus the doctest-corpus sweep against the pin — because\npassagemath's release QA has shipped broken backends\n([docs/passagemath_evaluation.md](docs/passagemath_evaluation.md)). It is the\noptional runtime; the monolithic image stays primary, and for untrusted or\nmulti-tenant use run the container regardless of runtime — a pip install has your\nuser's privileges, the container adds OS-level isolation.\n\nSource install, Docker Compose, and the Kubernetes Helm chart are in\n**[USAGE.md](USAGE.md)**.\n\n## Connect an MCP client\n\n**Claude Desktop** — add to `claude_desktop_config.json`:\n\n```json\n{\n  \"mcpServers\": {\n    \"sagemath\": { \"command\": \"uv\", \"args\": [\"run\", \"sagemath-mcp\"] }\n  }\n}\n```\n\nClaude Code, Codex CLI, Gemini CLI, and HTTP-transport setup are in\n[USAGE.md](USAGE.md#integrating-with-mcp-clients).\n\n## Try it\n\nPrompts a client can run once the server is connected:\n\n- **Damped harmonic oscillator** — \"Solve `x'' + 2·x' + 5·x = 0` with\n  `x(0)=1`, `x'(0)=0`, then verify the solution satisfies the ODE.\"\n- **General relativity** — \"On the hyperbolic upper half-plane with metric\n  `(dx² + dy²)/y²`, compute the Ricci scalar and confirm it is a constant\n  negative curvature.\"\n- **Coupled two-tank system** — \"Solve the linear ODE system for two mixing\n  tanks, then take the long-term limit of each concentration.\"\n\nEach builds an object once and explores it across calls — the case for\n`evaluate_sage` and its persistent session.\n\n## The 40 tools\n\nThe math tools use SageMath as the backend; full parameters and examples are in\n**[USAGE.md](USAGE.md#tool-reference)**.\n\n| Category | Tools |\n|----------|-------|\n| **Core execution** | `evaluate_sage`, `evaluate_sage_streaming` |\n| **Verification** | `verify_claim` |\n| **Calculus** | `differentiate_expression`, `integrate_expression`, `limit_expression`, `series_expansion` |\n| **Algebra** | `solve_equation`, `simplify_expression`, `expand_expression`, `factor_expression`, `calculate_expression`, `symbolic_sum` |\n| **Linear algebra** | `matrix_multiply`, `matrix_operation` |\n| **Differential equations** | `solve_ode` |\n| **Number theory** | `number_theory_operation` |\n| **Combinatorics** | `combinatorics_operation` |\n| **Graph / group theory** | `graph_operation`, `group_operation` |\n| **Elliptic curves / coding** | `elliptic_curve_operation`, `coding_theory_operation` |\n| **Polynomials / boolean / geometry** | `polynomial_ring_operation`, `boolean_algebra_operation`, `geometry_operation` |\n| **Statistics / probability** | `statistics_summary`, `distribution_operation` |\n| **Visualization** | `plot_expression`, `plot3d_expression`, `plot_multi_expression` |\n| **Numeric methods / vector calculus** | `find_root`, `vector_calculus_operation` |\n| **Session control** | `reset_sage_session`, `interrupt_sage_session`, `cancel_sage_session` |\n| **Named workspaces** | `start_sage_session`, `list_sage_sessions`, `stop_sage_session` |\n| **Diagnostics** | `check_sage_health`, `lookup_sage_doc` |\n\nPlus HTTP `/health` and `/ready` endpoints and 3 MCP resources (session\nsnapshots, monitoring metrics, doc links). Prefer `interrupt_sage_session` over\n`cancel_sage_session` — it stops a computation while keeping the session's\nvariables.\n\n## How it works\n\n```\n┌─────────────────────────────────────────────────────────────┐\n│  MCP Client (Claude Desktop, Gemini CLI, Codex CLI, ...)    │\n└───────────────────────────┬─────────────────────────────────┘\n                            │  MCP protocol (stdio or HTTP)\n                            ▼\n┌─────────────────────────────────────────────────────────────┐\n│  app.py + tools/ --- FastMCP 3.x                            │\n│  ┌─────────────┐  ┌──────────────┐                          │\n│  │ 40 MCP Tools│  │ 3 Resources  │   session.py routes each │\n│  └─────────────┘  └──────────────┘   client to its worker   │\n└───────────────────────────┬─────────────────────────────────┘\n                            ▼   one subprocess per session\n┌─────────────────────────────────────────────────────────────┐\n│  _sage_worker.py --- allowlist.py + security.py             │\n│  AST validation, then exec() in a persistent namespace      │\n│  (vars, functions and classes survive across calls)         │\n└─────────────────────────────────────────────────────────────┘\n```\n\n**Request flow:** MCP client → a tool in `tools/` →\n`SageSessionManager.get_or_create()` → `SageSession.evaluate()` → JSON request to\nthe `_sage_worker.py` subprocess → AST validation → `exec()` in the persistent\nnamespace → JSON response.\n\n- **Process isolation** — each session runs Sage in its own subprocess; a crash\n  or timeout in one cannot affect another.\n- **Stateful sessions** — variables, functions, and assumptions persist across\n  calls, enabling multi-step workflows.\n- **Deny-by-default** — a name is refused unless the generated allowlist offers\n  it or the caller's own code bound it. A helper a future SageMath adds is\n  refused until someone reviews it, rather than reachable the day it lands.\n\n## Security\n\nThe AST validator is **defence in depth against accidents and casual misuse — it\nis not a boundary against determined adversarial code.** **The container is the\nsecurity boundary.** The server has **no authentication**, so every default is\nloopback: `--host` defaults to `127.0.0.1`, the default transport is stdio, and\nCompose / Helm keep the endpoint off the network. Put something that\nauthenticates in front of it before exposing it.\n\nWhat the policy enforces: an **allowlist** (caller code may read only a name the\nserver offers or the caller itself bound); **no imports** by default; `eval` /\n`exec` / `compile` and runtime string evaluation blocked; dunder access blocked;\nthe external CAS interfaces and every file / network / persistence primitive\nremoved from the namespace, by provenance rather than by name. The container adds\na read-only root, dropped capabilities, `no-new-privileges`, and fork / memory\nceilings.\n\nFull threat model and the complete blocked / allowed tables:\n**[SECURITY.md](SECURITY.md)** and\n[USAGE.md § Security model](USAGE.md#security-model).\n\n## Docs & more\n\n- **[USAGE.md](USAGE.md)** — the full manual: every tool, how code is\n  interpreted, deployment, and the security model.\n- **[docs/mcp_quickstart.md](docs/mcp_quickstart.md)** — a first-session walk-through.\n- **[CHANGELOG.md](CHANGELOG.md)** · **[ROADMAP.md](ROADMAP.md)** ·\n  **[CONTRIBUTING.md](CONTRIBUTING.md)** · **[SECURITY.md](SECURITY.md)**\n\n## Requirements\n\nPython 3.12+ and a SageMath runtime (the container image bundles SageMath 10.9;\notherwise `sage` on `PATH`, or the `[passagemath]` extra). Built on\n[FastMCP 3.x](https://gofastmcp.com/).\n\n## Contributing\n\nIssues and pull requests welcome — see [CONTRIBUTING.md](CONTRIBUTING.md). Run\n`make lint` and `make test` before pushing (`git config core.hooksPath .githooks`\nwires the pre-push check). Roadmap and open work: [ROADMAP.md](ROADMAP.md).\n\n## License\n\nMIT — see [LICENSE](LICENSE). SageMath itself is GPL-2.0-or-later and is used as a\nseparate runtime; no SageMath source is redistributed in this repository.\n",
  "bytes": 11922,
  "sha": "bdc9f286611848bd2c04b15d80b50a1b8fbff289b367fc9b91269f95b4b46e33",
  "repo_slug": "xbp-europe/sagemath-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_xbp_europe_sagemath_mcp_0cd009cd/readme"
}