mathema mcp¶
mathema's library surface served as MCP tools, so an agent connects to the same machinery every other mode uses, the one resolver, the one gate, the reason-code registry, instead of shelling out and parsing tables.
pip install "mathema[mcp]"
mathema mcp serve [--root .]
serve runs the server on stdio. Without the optional extra installed
the command prints the install hint and exits 2.
Tools¶
| Tool | What it does |
|---|---|
resolve_target |
any target spelling (dotted name, module:function, file path) to its function keys and signatures |
adjudicate_target |
adjudicate one function, gated, with agent-shaped claim rows. include selects the row set: declared (default; what the surfaces actually state, which is what a re-check asks about), suggested, or all. Statements in claims are LINTED first, so a statement that does not parse comes back at parse cost with nothing adjudicated |
adjudicate_targets |
the same adjudication over SEVERAL targets in one call, column-oriented ({prefix, cols, rows}): the declared and verified stores are read once for the batch instead of once per target, and one envelope is returned instead of N |
verify_project |
the CI sweep: freshness, re-adjudication, record refresh, the gate; keys carries the sweep as structured per-key data (why, gate counts, claim rows) so nothing needs lines parsed |
describe_target |
one function's full detail view: identity, domains, claims with verdicts, tier ladder |
audit_targets |
the population report in the column-oriented compact shape; cols selects the columns, filter (explicit, never a default) keeps rows by derive_unlock class, claim status, or columnar substring (blocker~loop, ~mutual), same-dimension terms OR together, dimensions AND across, and a filter's column references are always computed so an empty result means "nothing matched", never "never looked" |
reason_code |
selective code lookup: names, numeric ids (2.18), or whole groups (loop, 2), singly or comma-separated; no argument returns the compact id/code/fixable index, never the full table, the machine twin of Reason codes |
claim_grammar |
the claim-language lexicon, exactly as the library states it |
parse_claim |
parse and validate one claim statement without adjudicating, the authoring-loop linter: resolved name/statement/relation/route/domain rendered back explicitly, or the grammar's own error. Pass target and it also checks the statement against that function's real signature (arity, parameter names, quantified names) by inspection |
suggest_claims |
candidate claims for one function, [name, statement, route, declared, aspect] rows, declares, never verifies, and a suggestion never gates until adopted; identical columns to mathema claims --suggest --format json |
pending_decisions |
the human-decision queue, read-only: [key, claim, kind, detail] rows for pending supersessions, stale acceptances, stale intent acceptances, standing accepted risks, unaccepted unknowns and falsifications, locked functions whose body moved, and moved rows for a record whose key no longer resolves while an unrecorded function has its form hash (the detail carries the mathema accept NEW --as reconciled --from OLD command a human runs); acceptance and unlock themselves stay CLI-only |
lock_target |
pin a function's form hash so verify fails if the body changes; the safe direction, so agents may do it. There is deliberately no unlock tool: a person runs mathema unlock |
project_index |
the generated .mathema/index.yaml map: system/module/function intents with acceptance rungs, keys, a sed-ready span per function, verified-record paths, concepts; run mathema audit --index (or mathema docsync) first if it reports the index missing |
implementation_coverage |
per-function implementation coverage without running the test suite: mathema's own probes and proofs supply the probe and derive sources, as [key, percent, potential, sources, test_stale, traced, remedy] rows plus the repo score; run_tests=true re-runs the suite under coverage first (slow, and it runs your tests) |
badges |
the three badges (implementation, intent, clarity) and their overall over a target set, or rootwide when empty, with [key, implementation, intent, clarity] rows and the ASCII triangle |
Which row set you get¶
adjudicate_target defaults to include="declared". That is the set a
re-check after editing a claim is actually asking about, and it is
much cheaper: on a real fixture, 3483 bytes of every-row output
becomes 959 (and 3481 becomes 480), because the suggestion battery is
not just serialized but fully adjudicated; skipping it is 5x-26x
faster as well as ~75-85% smaller.
A function with no declared claims returns no rows plus a hint
naming include="suggested", rather than silently handing back a
different kind of row than the one you asked for.
The two agent payload shapes¶
Adjudication rows (adjudicate_target, and mathema check --format
compact) carry, per claim: stance (a closed four-value fold of the
open verdict, supported/refuted/undecided/blocked, to
branch on, with the verbatim verdict beside it for fidelity),
source (which authoring surface the claim came from: declared,
docstring, decorator, types, suggested, builtin, ad_hoc),
and gates (whether the row counts toward passed, a falsified
suggestion never does, and the flag makes that self-evident in the
payload). Envelope rule: counterexample is present iff the row is
refuted, blocked_by iff it is blocked; presence is the signal, so
a missing key can't be misread the way null or "" can.
The adjudication rows are also what mathema verify --format json
emits per key, so a CI artifact and an agent call carry the same
vocabulary.
Population output (audit_targets, and mathema audit --compact) is
column-oriented: {"prefix": ..., "cols": [...], "rows": [[...],
...]}, column names once, the shared dotted key prefix factored out
of every row, and span in the sed-ready start:endp form so the
follow-up read is targeted; it is a ready-made sed -n range, not a
line count. (verify_project separately returns report, the human
prose, which is a different thing.) The caller names the columns it wants
(cols); the resolved selection is echoed back either way, and
analyses no chosen column needs are never computed at all.
One null policy across every cell: null means the analysis was not
computed; a computed-but-empty result is its typed empty value;
[] for lists, 0 for counts, "" for the blocker of a derivable
function. blocker holds exactly a bare reason-code key (a real
enum over the code table), with its decorations split into
blocker_params (the domain parameters a resolvable branch names)
and blocker_more (the +N further-blocked-kinds count). typed and
doc_quality are [n, m] pairs, never "n/m" strings to parse;
docsync is the weighted 0-100 CDD-compliance percent (one int);
mutates lists the module-level names the function writes through
(the strongest scope relationship); tested is the closed enum
"yes"/"no"/"no-report"/"outdated" (a missing coverage report
is a different fact from an uncovered function, and a report older
than the function's own source file supports neither answer).
Resources and prompts¶
The server also registers reference material and procedures, not just tools. A tool answers a question the agent already knew to ask; a resource is what it reads when it does not, and a prompt is a procedure delivered at the moment it chooses what to do.
| Resource | Content |
|---|---|
mathema://reference/grammar |
the claim language by example, generated from lexicon.LEXICON |
mathema://reference/codes |
the reason-code table with each code's derive_unlock and hint |
mathema://reference/verdicts |
how to read a verdict: the stance fold, why skipped is not falsified, and why passed can be true beside a refuted row |
| Prompt | Bound to |
|---|---|
claim_this_function(target) |
the authoring procedure with that function's real signature and current candidates resolved into it |
triage_repository(targets) |
orient, then narrow to what is both risky and unevidenced |
diagnose_falsification(target, claim) |
the code-wrong / claim-wrong fork, and why the judgement is handed back |
Everything served is generated from what core already owns, so it
cannot drift from the code it describes. The wall covers it: no
resource or prompt takes a verdict/accepted/stance/gates/
pin/verified_by parameter, none is named to suggest accepting,
unlocking or PIN entry, and the material says plainly that acceptance,
unlocking and the PIN are human acts done in the CLI. A test
asserts that over everything registered, not only over TOOLS.
lock_target is the deliberate one-way door: an agent may lock, and
only a person may unlock.
Wire format¶
Tool payloads go over the wire as compact JSON. The SDK pretty-prints
a returned dict with a hardcoded indent=2, which roughly doubles the
column-oriented payloads that exist precisely to stop repeating key
names, so the server serializes compactly at the boundary instead:
audit_targets 1468 -> 738 bytes, reason_code 4242 -> 2627. Prose-
heavy payloads gain little (claim_grammar, 9%), which is expected;
the saving is concentrated where the shape was already compact.
The tools themselves still return dicts, so they stay directly callable and testable, and nothing is duplicated into a structured content block.
What is deliberately absent¶
No tool makes an LLM call. No tool accepts a caller-supplied verdict:
claims are statements mathema adjudicates, and every verdict in every
payload is mathema's own. And there is no accept tool at all,
accepting evidence, owning risk, or marking a discovery is a human act,
done in the CLI (mathema accept), never exposed to
agent tooling.
Plugins¶
A mathema plugin can contribute tools by exposing an entry point in
the mathema.mcp_tools group whose callable returns plain functions.
The hook is fail-soft: a plugin that errors contributes nothing and
never breaks the server.