evidence-ledger-mcp
An MCP server for maintaining an evidence ledger that ties each claim to its source, locator, quote, relation, and provenance, with validation, checking, sharing, and PDF-linking tools.
Create new JSON Lines ledgers or import older/pre-schema ledgers, upgrading schema versions.
Add, update, batch-update, move, retrieve, search, and withdraw records without deleting history.
Automatically assign record/claim IDs, compute quote hashes, and date provenance.
Validate individual records or whole ledgers, including multi-ledger ID and claim consistency.
Find gaps, contestable claims without evidence, broken anchors, revision mismatches, missing bibliography keys, and quotes not found in provided or fetched source text.
Export ledgers or write HTML summaries at
full,masked, orlocatorprivacy levels with publication checks.Embed evidence into rendered work HTML as endnotes or a side panel.
Resolve citations against local PDFs or a link map and create private HTML copies with PDF passage links.
Report the server version and supported evidence-ledger schema version.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@evidence-ledger-mcp Create an evidence ledger and add a citation for my first claim."
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
evidence-ledger-mcp
Under active development. Both the schema and the server may change in breaking ways between versions until v1.0. Pin the version you use.
An MCP server through which agents keep an evidence ledger for the citations in a work: for each claim, the source that backs it, where in the source, a short quote and why the citation is needed. Co-authors, scripts or a second model can then check every citation against its source.
It is built for academics: authors and co-authors who write with LLMs and want every citation to stay checkable. The ledger format is an open schema, so a graduate school could require it for theses, and a journal could adopt it for submissions or build its own standard on it.
Supported schema: v0.5.1 · standalone schema repository (how schema versions are pinned).
The MCP server and its associated documentation use the MIT licence. The bundled schema uses CC BY 4.0, with attribution to Matthias Kloft. These licences do not cover ledger contents or third-party source material.
What is a ledger?
A ledger is a running record, like an accounting book, where each entry is written down once and can be checked later. An evidence ledger is such a record for claims and their evidence: one entry for each claim in a text, saying which source backs it and where. A source can be any document, such as an article, a book, an internal report, a protocol or a dataset description.
Related MCP server: Witness
The evidence ledger schema
Every ledger this server writes follows the evidence ledger schema (JSON Schema, draft 2020-12), currently v0.5.1. The schema is the contract: any tool, script or person can read, validate and check a ledger with it, with or without this server. Its README documents the purpose, the file shapes, every record field, quote hashing, a minimal example and the sources the fields draw on. The server bundles a pinned copy in schema/ and reports the supported version through schema_info.
A ledger is a header (schema_version, work and optional notes) followed by records. Records can link claims to reference passages, analysis outputs, or source code (see supported evidence). Each record links one claim in the work to its evidence:
Field | Required | Content |
| yes | Record and claim IDs; the server assigns them. Records about the same claim share a |
| yes | The claim, its type ( |
| yes | Where the claim sits in the work: its |
| yes | The evidence's key; references also need |
|
| |
| by type | Report or implementation artifact: path or HTTPS URL, exact-byte hash and locator; analysis may also identify a generating script. |
| Analysis dependencies as existing record IDs; cycles are refused. | |
| references | Where in the source: |
| A short quote from the source, and the phrase within it that carries the claim; | |
|
| How the source bears on the claim ( |
| yes | How the passage was found, and who assessed it (person or agent and model), when, and against which revision of the work. |
| The reason a citation was withdrawn; records are never deleted. |
A minimal record, as passed to add_record (the server adds the IDs, hash and date):
{
"claim": "Retrieval practice improves long-term retention.",
"claim_type": "empirical",
"contestable": true,
"anchor": {"exact": "testing improves later recall"},
"key": "example2020",
"doi": "10.1234/example",
"locator": {"page": 12},
"quote": "participants who were tested recalled more after one week",
"relation": "supports",
"justification": "Direct experimental evidence for the claim.",
"retrieved_via": "pdf"
}with provenance such as {"by_type": "agent", "by": "Claude Code", "model": "claude-opus-5-5"}. Ledgers are stored as JSON Lines; see docs/schema.md.
What it does
Write ledgers safely. Records are validated against the schema before they are appended. The server assigns record and claim IDs, computes quote hashes and dates the provenance instead of leaving them to the agent.
Keep the history. The server never deletes records; a withdrawn record keeps its reason.
Find gaps. Search by claim, source key or text, and list contestable claims still missing evidence.
Share without leaking sources. Exports and summaries come at three levels:
full(private),masked(short excerpts) andlocator(no quoted text). A publication check refuses to write a file that carries more source text than its level allows.Read it as a report. A standalone HTML summary, grouped by claim, prints to PDF from a browser.
See the evidence in the work. The claims are marked in the work's own HTML and linked to their evidence, as end notes or in a side panel.
Check it against the work and its sources. Find anchors that no longer appear in the work, records assessed against another revision, quotes that the source text no longer holds, and citation keys missing from the bibliography. Source text can be passed in, or the server fetches it from public URLs (web pages, plain text and PDFs with a text layer).
Installation
Requires Python 3.11 or newer. The server speaks MCP over stdio. With uv, run the released version from PyPI:
uvx --from evidence-ledger-mcp==0.4.0 evidence-ledger-mcpFor example, to add it to Claude Code:
claude mcp add evidence-ledger -- uvx --from evidence-ledger-mcp==0.4.0 evidence-ledger-mcpOther MCP clients take the same command and arguments. To run a source checkout instead, use uv sync, then configure the client to run uv with arguments run, --directory, the absolute path to the checkout, and evidence-ledger-mcp.
Pass absolute ledger paths to tools; relative paths resolve against the server's working directory.
Tools
Tool | What it does |
| Reports the server version and the supported schema version. |
| Creates a JSON Lines ledger with a header; |
| Converts a ledger of any older schema version, or a pre-schema hand-built list (YAML, JSON or JSON Lines), to a new JSON Lines ledger at the current version, keeping ids, provenance dates and quote hashes, and reports all that fails validation. |
| Validates and appends a record, refusing one whose |
| Changes fields of a record ( |
| The batch form of |
| Moves a record, withdrawn ones too, to another ledger of the project, keeping its ID, claim ID and provenance. Refused when the ID already exists in the target or the record fails the checks of the target or the |
| Returns one record by its ID. |
| Finds records by |
| Checks one record against the schema without writing it. |
| Checks a whole ledger and lists the problems found, including records of one claim that disagree on claim text or importance and, with |
| Marks a record withdrawn, with a reason, instead of deleting it. |
| Writes the ledger as JSON or JSON Lines at a chosen level to a new file. |
| Writes a standalone HTML summary grouped by claim to a new file. |
| Marks each claim in the work's rendered HTML and links it to its evidence, as end notes or in a side panel, in a new file. |
| Reads the ledger and reports gaps, anchors missing from the work, records from another revision and quotes not found in source text you pass in or the server fetches from public URLs. Writes nothing. |
| Matches each source to a PDF in a |
| Turns a summary or embedded HTML you received into a private copy ( |
Several ledgers per project
A project may keep more than one ledger, for example one for the schema and one for the server. The schema fixes IDs as R-0001 and C-0001, so per-ledger prefixes would need an upstream schema change; instead the IDs stay unique across the project's ledgers. Pass the other ledgers' paths as related to add_record, update_record, move_record and validate_ledger every time, and use move_record to move a record from one ledger to another. A moved ID is not reused as long as related lists every ledger of the project each time. A claim may then span related ledgers (a claim split by a move has records in both) and stays consistent as long as related is passed: add_record and move_record refuse a record whose claim text or importance differs from the claim's records in any related ledger, update_record copies a claim-level change to all of them, and validate_ledger reports a claim whose records disagree across ledgers.
Typical workflow
create_ledgeronce per work, next to the manuscript or code. For a ledger kept in an older YAML, JSON or JSON Lines file,import_ledgeronce instead; it never changes the source.add_recordwhenever a source is cited, with the claim, its anchor, the source key, locator, quote, relation and justification. The server fills in IDs, the quote hash and the provenance date.update_record, orupdate_recordsfor many corrections at once (for example fixing anchors), to correct records (a quote, locator or claim wording) instead of withdrawing and re-adding them. Provenance keeps the original assessment.find_recordswithmissing_evidenceandvalidate_ledgerbefore submission, to find claims still lacking evidence.check_ledgerwith the rendered work, its bibliography and the sources (as text or public URLs), to catch anchors, keys and quotes that no longer match.export_ledger,summarize_ledgerorembed_ledgeratmaskedorlocatorlevel to share the ledger with co-authors, reviewers or a journal.
The tool reference covers export levels, masking, embedding and every check in detail.
Available Tools
18 toolsadd_recordA
Append a record. The server assigns id, claim_id (unless given), quote_hash and the
provenance date, validates the result and returns the stored record. Mark the phrase
carrying the claim (a number, term or short statement) with optional quote_focus. Source
text belongs only in quote, quote_prefix, quote_suffix and quote_focus: write
justification, locator (including a section heading) and note in your own words, since
they are exported with masked and locator ledgers; a record that repeats 24 or more letters
and digits of its own quote in another field is refused, naming the field. A project with
several ledgers keeps ids unique across them: pass the other ledgers' paths as related
every time: the new id and claim_id are numbered past those of all of them, and claim
and importance must agree with the records of the same claim in them.
Inputs: path (required), record (required), provenance (required), related. Returns: result
check_ledgerA
Check a ledger without changing anything; the result holds record and claim ids, keys and
counts, never quote or source text. gaps is always returned: contestable records lacking a
locator, quote or justification, claims whose records are all withdrawn, and, when a
bibliography is known, record keys missing from it. Optional checks, each returned only when
its input is given: work reports anchors not found in it, or with only a marker, under
anchors (HTML is matched on its visible text; other files on their source text, so markup
inside an anchor reports not found: prefer the rendered HTML); revision lists records assessed
against another or an unknown provenance.manuscript_rev; sources maps a record key to the
source's full text and re-checks each quote against it (matched, not_found, no_source);
source_urls maps a record key to a public http(s) URL whose text (HTML, plain text or a
PDF with a text layer, up to 10 MB; hyphenation across PDF lines can make a quote
not_found) the server fetches itself and adds to sources (a key in both is an error).
Hosts that are not public, including via redirects, are refused; fetches that fail are
listed with a short reason under quotes.fetch_failed, and their records count as no_source.
bibliography is a .bib or CSL JSON file; for an HTML work without one, its ref-<key>
entries serve. Withdrawn records are skipped unless include_withdrawn.
Inputs: path (required), work, sources, revision, +3 more. Returns: result
create_ledgerB
Create a ledger file (JSON Lines) with a header; work needs title and type.
Inputs: path (required), work (required), notes, description. Returns: result
embed_ledgerB
Write a copy of the work's HTML (work, rendered by Quarto, Pandoc or any tool) with each
claim's anchor marked and linked to its evidence, to a new file. Both layouts end with an
"Evidence" section holding one card per claim; a button switches the reader between that
section and a side panel that opens the clicked claim's card (a bottom sheet on narrow
screens), and layout picks the view shown first; if no anchor is located, panel falls
back to endnotes and the result's layout says so.
Anchors that are only a marker, not found in the text or overlapping another claim's marked
text are listed under "Not located in the work" and returned in not_located (overlapping
maps a claim to the one it overlaps); a record key links to #ref-<key> when the work has
that bibliography entry. Levels and the publication check work as in summarize_ledger, and
out is never overwritten and must be in the work's folder. A full result holds whole
quotes: keep it private.
Inputs: out (required), path (required), work (required), level, +2 more. Returns: result
export_ledgerB
Export the ledger to a new file. masked keeps the first and last words of each quote,
locator no quoted text; a publication check runs before writing, and out is never
overwritten. max_words is allowed only for masked, from 1 to 25; None keeps the
default masking. With a budget, quote_focus centres the excerpt. A full export
holds whole quotes: keep it private.
Inputs: out (required), path (required), level, format, +2 more. Returns: result
find_recordsA
Find records by claim_id, source key, text in the claim, or contestable records lacking evidence (all filters combine); withdrawn records are left out unless requested.
Inputs: path (required), key, text, claim_id, +2 more. Returns: result
get_recordC
Return one record by its id.
Inputs: id (required), path (required). Returns: result
import_ledgerA
Import a ledger of any older schema version, or a pre-schema hand-built list, as a new JSON
Lines ledger at path, which must not exist. The source is YAML, JSON ({"ledger": header, "records": [...]} or a plain list of records) or JSON Lines (header on the first line, one
record per line). A plain list needs work (title and type); a source with a header must
not be given work. The header's schema_version is upgraded step by step to this server's
(a newer or unknown version is refused); records keep their ids, claim ids, provenance dates
and quote hashes (an older sha256-n1 hash stays valid), and the server assigns missing ids
and claim ids and the hash of a quote lacking one. Records that fail validation are still
written so that they can be fixed; the result holds path, records, from_version (null
for a pre-schema list), steps (such as "0.2.0→0.3.0") and problems (all found at once,
as validate_ledger reports them, empty if valid).
Inputs: path (required), source (required), work. Returns: result
move_recordA
Move a record (withdrawn ones too) from the ledger at path to the ledger at to,
keeping its id, claim_id and provenance, and return it. Refused when both are the same
file, the id is missing from path or already used in to, or the record fails the checks
of add_record against to and the related ledgers (header text, claim text or
importance of its claim). The claim may then span both ledgers: pass the project's other
ledgers as related to later calls so it stays consistent. Both files are rewritten
together, to first, all or none (on a write error the one already replaced is restored).
The id is not reused as long as related lists every ledger of the project each time.
Inputs: id (required), to (required), path (required), related. Returns: result
relink_htmlA
Give a reader links from a summary or embedded HTML file (from summarize_ledger or
embed_ledger, e.g. one a co-author sent) to their own PDFs in folder (best: a sources/
folder of only the cited PDFs named <citation key>.pdf, kept out of version control;
never a whole library) or in a link_map (a .private.json file as for resolve_links).
Records are read
from the data the file carries, sources matched as in resolve_links, and each evidence card
gets a "Your copy" line with an "Open PDF" button opening the PDF at the passage's page, or else
at the cited page, and, where the map gives an open link, an "Open in library" button
with the page appended (?page=N).
Writes <name>.private.html next to the original (a rerun replaces a copy it wrote, no
other file) and never changes
the original. The copy is marked private in its name, title and a banner, and is not indexed:
tell the user it links to files on this computer and must not be shared or published; they
share the original instead. Links are relative when the PDFs are inside the copy's folder
(so a team sharing that folder can open them), absolute file:// links otherwise; they work
when the copy is opened from disk, not from a website. Returns record ids by status.
Inputs: html (required), folder, link_map, bibliography. Returns: result
resolve_linksA
Resolve the ledger's sources against the reader's own PDFs and write the result to
link_file, which must end in .private.json (a rerun replaces one this tool wrote, no
other file): it holds paths on this computer, so do not share it.
Give folder, link_map or both. Best layout: a sources/ folder holding only the cited
PDFs, each named <citation key>.pdf, kept out of version control (copyrighted). A file
named exactly after the key is tried first; other names work through the matching below.
Do not pass a whole reference library as folder: thousands of PDFs are slow and the call
stops at the time limit with every source source_not_found. For a library, pass
link_map instead: the path of a .private.json file (JSON Schema link-map.schema.json)
with {"link_map_version": "0.1.0", "sources": {"<key>": {"file": "<pdf path>", "open": "<optional library link>"}}}. A mapped key searches only its PDF; a key missing from the
map is not_in_map, unless folder is also given, which is then searched for it. With
Zotero and Better BibTeX, build the map from JSON-RPC item.attachments(citekey) at
http://localhost:23119/better-bibtex/json-rpc, which returns {open, path} per attachment.
folder is searched recursively.
Where no passage is found, the link file still gives the PDF page of the record's
locator.page, read through the PDF's printed page labels or, without labels, taken as the
PDF page number (page_from says which). In a folder a source matches by the first
of: a file named <key>.pdf, its DOI on a PDF's first two pages, its ISBN on the first ten,
its title on the first two
(from bibliography, a .bib or CSL JSON file, which can also supply DOI and ISBN; titles
under 20 letters and digits count only with the first author's family name), each only if
exactly one PDF matches, so that a PDF citing the source does not; last, a file named after
the first author and year (e.g. Ioannidis 2005.pdf).
Each record is then
found (its passage is in the PDF, at the page given in the link file; this confirms the
record independently), passage_not_found, source_not_found, not_in_map or
not_searchable: finding a
passage needs its quote, whole or masked to its first and last words as in masked exports;
a locator export holds only the hash. The passage must hash to quote_hash, so
whitespace, punctuation, case and line-end hyphens may differ; a whole quote split by a
page break (with or without running heads or captions between its parts) is found and
marked across_pages in the link file. PDFs that cannot be read are listed with a short
reason under unreadable_files, and time_limit when reading PDFs stopped after 300 seconds.
The result holds record ids and file paths, never source text.
Inputs: path (required), link_file (required), folder, link_map, +2 more. Returns: result
schema_infoA
Report the server version and the evidence ledger schema version it supports.
Inputs: no named parameters. Returns: object
summarize_ledgerA
Write a standalone HTML summary of the records, grouped by claim, to a new file. Levels
and the publication check work as in export_ledger, and out is never overwritten. A
full summary holds whole quotes: keep it private. For a PDF, print the HTML from a
browser; its print style shows link targets and avoids splitting records where possible.
Inputs: out (required), path (required), level, include_withdrawn. Returns: result
update_recordA
Change fields of one record and return it. changes is merged into the record; a null
value removes a field. id, withdrawn, quote_hash and provenance cannot be changed,
and a withdrawn record cannot be edited. A new quote gets a new quote_hash; a removed one
drops it. The record's provenance is not touched: it records the original assessment (who,
which model, which manuscript revision, when), not later edits. A change to claim or
importance is copied to every record with the same claim_id; a claim may span related
ledgers, so pass their paths as related (the same list every time) to update its records
there too. The same checks as add_record apply to every affected ledger, and nothing is
written when one fails.
Inputs: id (required), path (required), changes (required), related. Returns: result
update_recordsA
Change fields of several records in one call and return each item's record in its final
state (two items on one record return the same, fully updated record).
updates is a list of {"id": "R-0001", "changes": {...}}; each changes works as in
update_record, and provenance stays as it was. Updates apply in order, so a later one sees
an earlier one (a duplicate id is allowed), and claim-level changes are copied to the claim's
other records, also in the related ledgers (pass the same list every time). Every affected
record is checked against the final state of all ledgers. If any item is refused, the error
lists all refusals, each prefixed with the item index and record id, and nothing is written;
otherwise the changed ledgers are rewritten together, related ones first and the edited one
last: all files or none (on a write error the files already replaced are restored).
Inputs: path (required), updates (required), related. Returns: result
validate_ledgerA
Validate a whole ledger; returns the problems found, empty if valid. Pass the other
ledgers of the project as related (the same list as for add_record) to also report ids
used in more than one of them (R-0001: id also used in <ledger>) and claims whose records
disagree on claim text or importance across them (C-0001: records disagree on claim text across related ledgers).
Inputs: path (required), related. Returns: result
validate_recordA
Validate one ledger record against the schema; returns the violations, empty if valid.
Inputs: record (required). Returns: result
withdraw_recordB
Mark a record withdrawn with a reason instead of deleting it.
Inputs: id (required), path (required), reason (required). Returns: result
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 18 tools
Most tools target clearly distinct operations across record lifecycle, ledger lifecycle, validation, and output/linking. The main overlaps are check_ledger vs validate_ledger and the export_ledger/summarize_ledger/embed_ledger family, but the descriptions provide enough differentiation.
Names are consistently snake_case and overwhelmingly follow verb_noun patterns such as add_record, update_records, and validate_ledger. schema_info is the lone noun-only exception, which is a minor deviation.
At 18 tools, the server is slightly above the typical 3–15 well-scoped band. However, the evidence-ledger domain is rich and each tool maps to a distinct operation, so the set does not feel excessively bloated.
Coverage is strong: create/import ledgers, add/get/find/update/move/withdraw records, validate/check, export/summarize/embed, and source-link resolution are all present. Minor gaps remain around ledger-level metadata updates or deleting an entire ledger, but core workflows have no major dead ends.
Maintenance
Related MCP Connectors
Tamper-evident proof creation and verification for AI agents via MCP, A2A, and REST.
Evidence-readiness MCP server: validate, audit, and score briefs, memos, and evidence packs.
Host any HTTP API as MCP tools with a tamper-evident cross-agent settlement ledger.
Evidence-graded agent-work lanes, bid advice, live agent jobs and a hash-chained evidence ledger.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceAn MCP server providing an append-only, hash-chained evidence ledger for agent actions, where every record is a tamper-evident receipt cryptographically bound to all prior records and persisted as human-readable JSONL local state. It exposes tools to append records, verify chain integrity (pinpointing tampering), query records by actor/action/target/time, and fetch ledger stats—with no update or delete capabilities by design.1Apache 2.0
- AlicenseNot gradedqualityBmaintenanceProvides evidence-oriented MCP service for cryptographically identified agents, bounded public contracts, privacy-preserving records, and append-only audit.Apache 2.0

operations-pulseofficial
AlicenseNot gradedqualityBmaintenanceEnables AI assistants to run local-first operations checks, review evidence, and manage durable tickets in a SQLite-backed ledger through self-describing MCP tools.MIT- AlicenseAqualityBmaintenanceFree seven-tool proof loop for MCP agents: scoped sessions, bounded authorization, typed outcomes, evidence-bound claims, and tamper-evident local closure. Upgrade to Complete Local for durable memory, recovery, signed traces, release verification, and multi-agent workflows.7Apache 2.0