mathgr is a Python/SymPy and MCP port of
tririver/MathGR, a symbolic toolkit for
tensor calculus, general relativity, ADM/FRW decompositions, perturbation
series, integration by parts, and TeX export.
The package has two user-facing interfaces:
- Python interface: normal Python/SymPy API for scripts, notebooks, tests, and direct human use.
- MCP interface: designed for Codex, Claude Code, and other coding agents. It
accepts Python-like expression strings, auto-declares tensors and indices, and
exposes tools such as
mathgr_compute,mathgr_parse, andmathgr_tex. Example agent call:mathgr_compute("Simp(Dta(U('α'), D('β')) * f(U('β')))").
Wolfram is not needed. The test suite translates upstream MathGR tests and notebook cells to pytest, with an optional Wolfram oracle test for checking the original package.
MathGR-Py is Unicode-first for common Greek physics symbols. Prefer names such
as φ, δφ, φ0, ζ, ε, η, α, and β in Python and MCP expressions.
Use normal index constructors with UTF-8 labels when useful, for example
U1("α"), D1("β"), or DN("i").
Wolfram Language is concise and pleasant for human symbolic work. This port exists because Python is easier for AI coding agents to inspect, run, test, modify, and expose through MCP tools. The initial motivation was better agent interaction with MathGR; it is also useful for humans who prefer a Python/SymPy workflow.
Use the Python interface for scripts, notebooks, tests, and direct work in a Python process.
From this repository:
uv syncUse it in another editable environment:
python -m pip install -e .Requirements:
- Python 3.12 or newer
- SymPy 1.13 or newer
Most notebooks can start from the package root import:
import sympy as sp
from mathgr import *For scripts and libraries, prefer explicit imports from mathgr,
mathgr.tensor, mathgr.gr, mathgr.decomp, mathgr.frwadm, mathgr.util,
mathgr.ibp, and mathgr.typeset.
Note: from mathgr import tensor imports the tensor-head factory. To inspect
the mathgr.tensor submodule itself, use:
import importlib
tensor_module = importlib.import_module("mathgr.tensor")Declare indices and tensors:
from mathgr import LatinIdx, declare_idx, tensor, Dta, Simp
u, d = declare_idx("U", "D", dim=3, index_set=LatinIdx)
f = tensor("f")
expr = Dta(u("a"), d("b")) * f(u("b"))
print(Simp(expr))Output:
f(U('a'))
Add tensor symmetries:
from mathgr import DeclareSym, Symmetric
S = tensor("S")
DeclareSym(S, (d, d), Symmetric((1, 2)))
print(Simp(S(d("b"), d("a")) - S(d("a"), d("b"))))Output:
0
Differentiate symbolically:
from mathgr import Pd
h = tensor("h")
expr = f(u("a")) * h(d("b"))
print(Pd(expr, d("c")))Pd applies product, sum, and supported power rules and stores derivatives as
PdT(expr, PdVars(...)).
Use a metric and GR identities:
from mathgr import LatinIdx, declare_idx, tensor, UseMetric, R, CovD, Simp
u4, d4 = declare_idx("U4", "D4", dim=4, index_set=LatinIdx)
g4 = tensor("g4")
UseMetric(g4, (u4, d4))
second_bianchi = (
CovD(R(d4("a"), d4("b"), d4("c"), d4("d")), d4("e"))
+ CovD(R(d4("a"), d4("b"), d4("d"), d4("e")), d4("c"))
+ CovD(R(d4("a"), d4("b"), d4("e"), d4("c")), d4("d"))
)
print(Simp(second_bianchi))Output:
0
Use UTF-8 Greek scalar names directly:
import sympy as sp
import mathgr.typeset as typeset
from mathgr import D1, Pd, ToTeXString
δφ = sp.Symbol("δφ")
typeset.ToTeXTemplate = False
print(Pd(δφ, D1("α")))
print(ToTeXString(Pd(δφ, D1("α"))))Output:
_PdT(δφ, _PdVars(D1('α')))
\partial_{α} δφ
Split time and space indices:
from mathgr import Decomp0i, Simp, tensor
from mathgr.decomp import UTot, DTot
f = tensor("f")
expr = Decomp0i(f(UTot("mu"), DTot("mu")))
print(expr)
print(Simp(expr))Output:
f(UE(0), DE(0)) + f(UP('mu'), DN('mu'))
f(UE(0), DE(0)) + f(UP('a'), DN('a'))
Expand perturbation series:
import sympy as sp
from mathgr import Eps, SS, OO
x = sp.symbols("x")
expr = (1 + Eps * x) ** 3
print(SS(2)(expr))
print(OO(2)(expr))Output:
3*Eps**2*x**2 + 3*Eps*x + 1
3*x**2
Export TeX:
import mathgr.typeset as typeset
from mathgr import DN, UP, Pd, tensor
previous = typeset.ToTeXTemplate
try:
typeset.ToTeXTemplate = False
f = tensor("f")
print(typeset.ToTeXString(f(UP("a"), DN("b"))))
print(typeset.ToTeXString(Pd(f(DN("i")), DN("j"))))
finally:
typeset.ToTeXTemplate = previousOutput:
f^{a}_{b}
\partial_{j} f_{i}
Run examples:
uv run python -m examples.general_presentation
uv run python -m examples.decomp_example
uv run python -m examples.second_order_pert
uv run python -m examples.zeta_gauge_action_from_delta_phiLarger notebook ports may be expensive if you enable full action computation.
Many expose flags such as main(compute_action=False).
See doc/manual.md for a more complete API manual.
Create release/source archives from git so virtual environments and caches stay out of the upload:
git archive --format=zip --output mathgr-py-src.zip HEADDefault local test workflow:
tools/test.shThis runs fast tests serially, then the slow symbolic bucket with
pytest-xdist; override workers with PYTEST_SLOW_WORKERS=8 tools/test.sh.
Use uv run pytest -q only when you want the full serial release check.
Use the MCP interface when you want a coding agent to call MathGR directly after startup. The MCP surface is intentionally different from raw Python: agents send Python-like expression strings and the server auto-declares common objects.
For Codex or Claude Code, you usually do not need to install the MCP server by hand. Start your agent in this repository and ask:
Read the Agent MCP Install Recipe at the end of README.md and install MathGR MCP
for your own future sessions.
The agent should follow that recipe, update its own MCP configuration, then tell
you to start a new Codex or Claude Code session. New sessions can call the
mathgr MCP tools immediately.
Manual smoke check from this repository:
uv sync
timeout 2s uv run mathgr-mcpExit code 0 or 124 from timeout is acceptable. A Python traceback is not.
Portable user-level install:
tools/install_mcp.shThis stores a uvx --from git+https://github.com/tririver/mathgr-py
command in the agent MCP config, so new sessions can start MathGR from any
directory and do not depend on this checkout path still existing. Use
tools/install_mcp.sh --dev-local only when this repository path is permanent
and you intentionally want MCP sessions to run the local checkout.
Core tools:
mathgr_manual: read the full manual or a named sectionmathgr_compute: first-choice tool for almost all MathGR calculations; auto-declare and evaluate a Python-like expression or notebook blockmathgr_tex: render an expression to TeXmathgr_context_get: list stored context declarations and expression source definitionsmathgr_context_clear: reset the default or named contextmathgr_context_save,mathgr_context_load: save/load context JSON filesmathgr_parse: only for debugging; dry-run a Python-like expression and show inferred declarations plus reproducible Pythonmathgr_inspect: only for debugging; list indices, free/dummy labels, tensor heads, derivative nodes, andPm2nodesmathgr_script: only for debugging/reproduction; export Python for a structured calculationmathgr_capabilities: compact API group listmathgr_run_python/mathgr_eval: last-resort debugging escape hatch for raw trusted Python
Agents should use mathgr_compute first for almost every calculation. The
compute tool is easier than raw Python because it auto-declares ordinary scalar
names, tensor heads, index families, and a persistent default context. For most
derivations, do not predefine scalar symbols, tensor heads, or temporary names
just to make them exist; write the calculation directly, using multi-line
mathgr_compute assignments only when a derived expression should be reused.
Use mathgr_parse, mathgr_inspect, and mathgr_script only for debugging.
Use mathgr_run_python /
mathgr_eval only when mathgr_compute cannot express the workflow.
For ordinary calls, pass only the expression string and omit optional fields
such as context, output, and timeout_seconds. JSON objects are only the MCP
transport format; examples and traces should prefer mathgr_compute("...").
Structured MCP tools default to timeout_seconds=0, which runs in-process for
low latency. Set a positive timeout only for risky or potentially long symbolic
calls that need subprocess cancellation.
Example:
mathgr_compute("Simp(Dta(U('α'), D('β')) * f(U('β')) + x)")
The MCP server infers:
Dim = sp.Symbol("Dim")
U, D = declare_idx("U", "D", dim=Dim)
f = tensor("f")
x = sp.Symbol("x")Typical MCP calls:
mathgr_compute("Simp(Dta(U('α'), D('β')) * f(U('β')))")
mathgr_compute("Pd(δφ, D1('α'))")
mathgr_compute("Decomp0i(f(DTot('a')) * f(UTot('a')))")
mathgr_compute("Ibp(y * Pd(x, D('i')))")
mathgr_compute("OO(2)((1 + Eps*x)**3)")
mathgr_compute("Simp(lhs - rhs)")
mathgr_tex("Pd(f(U('a')), D('i'))")
Perturbation expansion:
mathgr_compute("""
L2 = Simp(OO(2)(TSeries(L, (Eps, 0, 3))))
result = L2
""")
Derivative conventions:
| notation | MCP expression |
|---|---|
dot(f) |
Pd(f, DE(0)) |
partial_i f |
Pd(f, DN("i")) |
partial_i partial_j f |
Pd(Pd(f, DN("i")), DN("j")) |
When calling the MCP tool with a multi-line block, pass the block body as
expr. Do not include Python triple-quote delimiters inside the MCP expr
value. Triple quotes are only a convenient way to show multi-line examples in
this README.
Flat-gauge ADM scalar-field setup:
mathgr_compute("""
N = 1 + Eps*α
Ni = Eps*Pd(β, DN("i"))
φ = φ0 + Eps*δφ
φdot = Pd(φ, DE(0))
gradφ = Pd(φ, DN("i"))
result = Ni + φdot
""")
For the full ADM scalar-field density, use the FRW ADM helpers as the starting point and then extract the desired perturbative order:
mathgr_compute("""
φ = φ0 + Eps*δφ
i = sp.Wild("i")
flat_rules = {b(DN(i)): 0, Pd(φ0, DN(i)): 0}
L = frwadm.Sqrtg * (frwadm.RADM()/2 + frwadm.DecompG2H(lambda: X(φ)) - V(φ))
L_flat = TReplace(flat_rules)(L.xreplace({ζ: 0}))
L2 = frwadm.Simp(OO(2)(TSeries(L_flat, (Eps, 0, 3))))
result = L2
""", timeout_seconds=30)
Constraint extraction by integration by parts:
mathgr_compute("""
beta_terms = Simp(IbpVariation(L2, β))
constraint_beta = Simp(beta_terms.coeff(β))
result = constraint_beta
""")
Use IbpVariation(expr, β) to move derivatives off β, then take the
coefficient of β. Use IbpNB when you want boundary holders dropped.
TeX output:
mathgr_tex("L2", fragment=True)
mathgr_compute("Simp(lhs - rhs)")
Use raw MCP TeX for exact, checkable output tied to the expression tree. For a
paper or note, hand-clean notation after the algebra is verified; keep the
machine expression for zero-checks such as Simp(lhs - rhs).
Override dimensions when needed:
{"index_dims": {"U/D": 3}}Use the default context for normal multi-step calculations. Omit context
unless you intentionally need parallel or incompatible branches:
mathgr_compute("""
trace = Dta(U('a'), D('a'))
simplified = Simp(Dta(U('a'), D('b')) * f(U('b')))
result = trace
""")
mathgr_compute("simplified")
Result:
f(U('a'))
If context is omitted, mathgr_compute uses and auto-creates "default".
Assignments in a compute block persist in that context:
mathgr_compute("""
trace = Dta(U('a'), D('a'))
result = Simp(trace)
""")
mathgr_compute("trace")
mathgr_context_get()
result = ... controls the returned value but is not persisted as context
state. Use a regular assignment, or store_as, for durable values.
Named contexts are for explicit forks only, such as comparing baseline and
gauge_fixed states. Do not create new contexts for ordinary retries or probes;
keep using the default context so traces remain readable.
Top-level declarations persist too:
mathgr_compute("""
gMcp = tensor("gMcp")
UseMetric(gMcp, (U, D))
F = tensor("F")
DeclareSym(F, (D, D), Antisymmetric((1, 2)))
result = Simp(F(D('a'), D('a')))
""")
Compute blocks intentionally reject imports, loops, function/class definitions,
with, private attributes, and unsafe builtins. Restricted expression-only
lambdas are allowed for local hooks and callbacks. If a scalar name collides
with a preloaded API name, pass symbols=[...] to make that name a SymPy symbol
for the call/context.
Context files survive agent restarts:
mathgr_context_save(context="default", path=".mathgr/contexts/default.json")
mathgr_context_load(path=".mathgr/contexts/default.json", context="default")
Security note: raw Python tools are intended for trusted symbolic snippets from the agent session, not as a sandbox for hostile code. They run snippets in a child process with a timeout, restore MathGR global state after each call, and block common filesystem/shell/import paths, but Python execution is not a security boundary.
This section is written for Codex, Claude Code, or another coding agent. If a
human asks you to install MathGR MCP, execute these steps from this repository.
Use the portable install by default. It records a GitHub uvx command in the
user MCP config, not the current checkout path, so it keeps working after a
temporary Docker or scratch directory is removed.
- Install for all available supported agents:
tools/install_mcp.shEquivalent explicit Codex command:
codex mcp remove mathgr || true
codex mcp add mathgr -- env -u VIRTUAL_ENV -u PYTHONPATH \
/usr/local/bin/uvx --python 3.14 \
--from git+https://github.com/tririver/mathgr-py \
mathgr-mcp
codex mcp get mathgrEquivalent explicit Claude Code command:
claude mcp remove mathgr || true
claude mcp add --scope user mathgr -- env -u VIRTUAL_ENV -u PYTHONPATH \
/usr/local/bin/uvx --python 3.14 \
--from git+https://github.com/tririver/mathgr-py \
mathgr-mcp
claude mcp get mathgrOnce release tags exist, prefer a pinned source such as:
--from git+https://github.com/tririver/mathgr-py@v0.1.0Use this only when the absolute repository path is persistent. This records that path in the agent MCP config so MCP sessions run your local checkout.
- Resolve the absolute repo path:
REPO="$(pwd)"- Install runtime dependencies and smoke-check the stdio MCP command:
uv --directory "$REPO" sync
timeout 2s uv --directory "$REPO" run mathgr-mcpExit code 0 or 124 from timeout is acceptable. Any Python traceback is not
acceptable; debug before continuing.
- Install for Claude Code if
claudeis available:
claude mcp remove mathgr || true
claude mcp add --scope user mathgr -- uv --directory "$REPO" run mathgr-mcp
claude mcp get mathgrThis user-scope install makes MathGR MCP available to new Claude Code sessions
on this machine as long as $REPO keeps existing. Claude Code can also use this
repo's project-scoped .mcp.json when started in the repo and approved by the
user.
- Install for Codex if
codexis available:
codex mcp remove mathgr || true
codex mcp add mathgr -- uv --directory "$REPO" run mathgr-mcp
codex mcp get mathgrThis user-level install makes MathGR MCP available to new Codex sessions on this
machine as long as $REPO keeps existing. Codex can also use this repo's
project-scoped .codex/config.toml after the repo is trusted.
For disposable containers or fresh scratch directories, use the portable uvx
install above. If the image should run a baked-in checkout instead, copy the repo
to a stable path such as /opt/mathgr-py and use the development checkout
install with REPO=/opt/mathgr-py.
- Start a new Codex or Claude Code session so the MCP server is loaded at startup.
- In Codex,
/mcpshows active MCP servers. - In Claude Code,
/mcpshows connection state and tool count. - Ask the agent to call
mathgr_capabilitiesto verify MathGR tools are live.