ledger
This server provides tamper-evident, hash-chained action logging for MCP agents: append records, verify chain integrity, prove your own conduct, audit a peer's ledger, and declare out-of-band breaks.
ledger_append: append any JSON action record to the chained log; returns the record's chain hash (auto-addstsif absent).ledger_verify: verify the full hash chain —ok=trueevery row verified,ok=nullbounded/unverified prechain rows (not green),ok=falsenames the first tampered line;strictmakes any unchained row a hard failure.prove_my_conduct: log a batch of actions under a namespace and return{rows, head_hash, chain_verified}— one hash to hand a principal as proof.verify_peer_ledger: recompute the chain over another agent's exported JSONL text; returns{ok, rows, first_break, declared_breaks}with first_break as an integer line number, without touching your ledger.declare_break: append a declaration pinning an out-of-band broken line; the verdict stays bounded (ok=null, never green) and it refuses if nothing is broken.
Click on "Install 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., "@ledgerLog this action and verify the chain integrity."
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.
arcaeon-ledger
Observability tools show you what your agent did. arcaeon-ledger lets you prove it.
Every record is hash-chained to the one before it. Edit a row, delete one, or
reorder history, and every later link breaks — verify names the exact line.
You own the record, and you can prove it wasn't altered. Zero dependencies, one
JSONL file, two verbs.
pip install arcaeon-ledger # then: from arcaeon_ledger import Ledgerfrom arcaeon_ledger import Ledger
log = Ledger("agent.log.jsonl")
log.append({"tool": "web.search", "query": "weather in LA", "result_ok": True})
log.append({"tool": "payment", "amount": "49.00", "currency": "USD"})
log.verify() # VerifyResult(ok=True, rows=2, chained=2, ...)Tampering is caught, not hoped against:
# someone edits row 2's amount in the file by hand...
log.verify() # VerifyResult(ok=False, first_break="line 2: chain mismatch")CLI (wire it into CI or a pre-ship gate — a tampered log exits nonzero, and a log that could only be partially vouched for no longer exits like a fully verified one):
python -m arcaeon_ledger.cli append agent.log.jsonl '{"tool":"search","ok":true}'
python -m arcaeon_ledger.cli verify agent.log.jsonl
python -m arcaeon_ledger.cli verify --strict agent.log.jsonlverify exit codes (0.5.7):
exit | meaning |
| fully verified — every row checked, chain intact ( |
| broken — a break was found ( |
| verified within scope only ( |
A CI gate should treat only 0 as green:
python -m arcaeon_ledger.cli verify agent.log.jsonl
case $? in
0) echo "fully verified" ;;
3) echo "chain intact but prechain rows skipped unverified — inspect, or use --strict" ; exit 1 ;;
*) echo "ledger broken" ; exit 1 ;;
esacProve who acted, not just the order
A hash chain proves sequence integrity — it can't prove who wrote each entry or
whether they were allowed to. Attach an authority block to bind the actor and
their permission surface into the chained (tamper-evident) row:
from arcaeon_ledger import Ledger, authority
log = Ledger("agent.log.jsonl")
log.append(
{"tool": "payment", "amount": "49.00"},
authority=authority(
"agent://billing-7",
capability_version="v3", # what they were allowed to do
tool_schema={"name": "payment", "args": ["amount"]}, # hashed, not just named
time_source="ntp", # trust surface of the clock
),
)Now the audit question sharpens from "was this edited?" to "was this edited and was the writer authorized?" — editing the principal, capability, or schema hash breaks the chain like any other tamper. This composes tamper-evidence with permission-replay. (Shipped in response to community feedback on launch.)
Related MCP server: Agent Audit Trail MCP Server
Why this exists
The loudest unmet pain for agent builders in 2026 is the reliability/audit gap:
an agent "completes" a task and the result is quietly wrong, and you can't
reconstruct — or prove — what actually happened. Observability platforms trace
runs; none give you a tamper-evident, portable, ownable record. Regulation
is arriving too: the EU AI Act requires high-risk systems to technically allow
automatic recording of events over their lifetime (Art. 12(1)) and requires
providers and deployers to keep those logs, to the extent under their control,
for at least six months (Art. 19(1), Art. 26(6)). The Act mandates recording
and retention — tamper-evidence is not its word, it is ours: when someone asks
whether a retained log is still the log, that question needs an answer stronger
than trust. arcaeon-ledger is the smallest honest version: a cryptographically
chained action log you drop in, own, and verify.
How the chain works
chain = sha256(prev_chain + json.dumps(row_without_chain, sort_keys=True, ensure_ascii=False))[:32]
Note the separators: the chain body uses Python's default ", " / ": "
spacing, not the compact json-c14n:v1 form the artefact digests use. A
cross-language verifier has to reproduce that spacing exactly.
The chain value is truncated_sha256_128 — the first 32 hex chars (128 bits)
of SHA-256, not the full digest. Named so nobody cites it as full SHA-256:
128 bits is plenty for edit/accident detection, thinner if you want the chain
itself to be expensive to grind after a rewrite (credit: atomic-raven's review).
Each row commits to the entire history before it. The first row chains from a
fixed "genesis" seed. Rows without a chain field are tolerated only before
the first chained row (so you can adopt it on an existing log); an unchained row
appearing after the chain begins is itself flagged. On a mismatch, verify
keeps going from the claimed value so it counts later damage honestly instead of
cascading one break into noise.
What it proves — and the five things it doesn't
Being precise here is the product, not a disclaimer. A hash chain proves the
recorded content of each row was not altered in place after writing:
mid-file edit, delete, and reorder all break it and verify names the row.
One word in that sentence changed in 0.5.8, and the reason is the kind of thing this section exists for. It used to say "the recorded bytes", which claims more than the chain does. The chain is computed over each row parsed back from the file, and the reader normalises byte sequences it cannot decode — so two different byte strings inside such a region read identically and produce the same verdict. What is protected is the meaning of every row, not the exact bytes of the file. If you need byte-level custody, hash the file itself alongside this.
It does not by itself prove five other things:
1. Truncation. Lop off the most recent rows and what remains verifies clean — no append-only chain catches this alone. Close it by publishing the head somewhere outside your own control, on a cadence:
pin = log.head().as_pin()
# -> "arcaeon-ledger head chain=9f3c… rows=204 as_of=2026-08-13T17:40:00Z"
# post `pin` to a git commit / public comment / notarization anchor.
# a reader compares a fresh head() against the last pin; a truncated or
# re-minted history disagrees. the MAX gap between pins is your security
# parameter, not the average — an attacker picks the gap.2. Truth. The chain notarizes whatever was written — a tamper-evident record of a hallucination is still a hallucination with a checksum. To make a row speak about the world, hash a re-fetchable artefact (URL+bytes, a snapshot, tool stdout) and store that digest in the row, so a third party can re-get it and compare.
3. Authorship. authority() (above) records who-claimed-what, but it is data
in the row, not a signature — a rewriter who re-mints from genesis re-mints it too.
External head-anchoring (#1) is the thing a re-minter cannot advance.
4. Fabricated-legacy-prepend. Rows with no chain field are tolerated before
the first chained row — that is deliberate, so you can adopt the chain on top of an
existing log without rewriting its history. But skipped rows are unverified rows,
and the verifier cannot tell real legacy history from a fabricated prepend. So
(0.5.7) a non-strict verify that skipped any rows never mints a green: ok is
None — "no break found, verified within scope" — falsy, with the scope in-band
(verified_scope: "bounded_prechain_skipped") and the count in prechain; the CLI
exits 3, not 0. Only a scan that checked every row returns ok=True. If your
log is chained from genesis and must have no legitimate legacy rows, pass
verify(strict=True) / --strict — it treats any unchained row as a break, hard
red. (An unchained row inserted after the chain begins is already flagged in
every mode.)
5. Completeness. This is the big one, and it is structural: the agent decides
what to call append on. A tamper-evident log of the calls an agent chose to
report is still self-report. Nothing inside this library can close that, because
anything the agent invokes, the agent can decline to invoke.
Close it by moving the pen out of the agent's reach — record at the seam instead, in a separate OS process the agent does not own, cannot skip, and cannot see:
pip install arcaeon-adapter
python -m arcaeon_adapter --ledger seam.log.jsonl -- <your mcp server command...>arcaeon-adapter is a
stdio proxy that forwards JSON-RPC byte-for-byte between an MCP client and server,
writing one hash-chained row per tools/call to its own ledger. Wrapping it around
this library's own MCP server produced the number that makes the point: the
server's own diary wrote 0 rows while the seam log captured 5. The gap
between what a system reports about itself and what the seam observed is the
thing worth measuring.
Scoped honestly, the primitive is "this file was not rewritten in place" — small,
true, and testable. The layers above (external anchoring via head(), artefact
binding, signed authorship, seam recording) are how you extend it toward a full
evidence claim.
verify() on missing or empty ledgers
The two look like the same thing ("no data"), and verify() keeps them
apart, on purpose:
Ledger("never/written.jsonl").verify()
# VerifyResult(ok=False, rows=0, first_break="unreadable: [Errno 2] No such file...")
open("touched/empty.jsonl", "w").close()
Ledger("touched/empty.jsonl").verify()
# VerifyResult(ok=None, rows=0, chained=0, first_break=None, verified_scope="empty")A path that was never created can't be vouched for — ok=False, "unreadable,"
same as any other read failure. A path that exists and is genuinely empty has
zero rows to tamper with, but zero rows checked is not a green either (since
0.5.8): ok=None, rows=0, verified_scope="empty", falsy, CLI exit 3. Automation
that branches on verify().ok gets a red for the missing file and a
not-a-pass for the empty one; read first_break and verified_scope to tell
the two apart by name.
When the log was written out of band: declare the break, don't re-forge it
Sooner or later something writes to your JSONL without going through append() —
a script, an incident, a person with an editor. The chain breaks there and stays
broken, because that is the true record. Your two obvious options are both bad:
live with a permanent red that tells a reader nothing, or recompute the chain so
the file goes green — which is forging it, and a chain you can silently re-forge
is not evidence of anything.
declare_break is the third option. It appends a row naming the break:
from arcaeon_ledger import declare_break, verify_file
declare_break("agent.log.jsonl", 25,
"Written out of band 2026-08-15 by a session hand-appending JSON "
"instead of calling append(). Content is true and preserved verbatim; "
"no chain value was ever computed for it, so none can honestly be supplied.")
r = verify_file("agent.log.jsonl")
r.ok # None — bounded, NOT True. Falsy.
r.verified_scope # "bounded_declared_break"
r.breaks # 0
r.declared # ["line 25: declared break (Written out of band 2026-08-15 ...)"]The break stays a break, forever, in declared. What changes is that a known,
explained break stops masquerading as an unexplained one — and the orphan's exact
bytes are pinned by sha256, so editing that line afterwards turns the file red
again. It never returns ok=True. Only a scan that checked every row does
that, and an excused row was not checked. verify(strict=True) ignores
declarations entirely.
What this does not do, said plainly: it is a record device, not a
cryptographic one. Anyone who can write the file can write a declaration, so it
raises no bar at all against an attacker who already has write access. It defends
against forgetting, not against tampering. It cannot tell an honest out-of-band
append from a malicious one — why is an unverified human sentence. And it can
only declare breaks verify() already found; it does nothing about breaks nobody
noticed. Use it to keep an honest incident legible, never as a way to make a
ledger green.
Bind what the agent actually read (artefact-binding)
The chain proves a row wasn't edited. It does not prove the row was ever true —
it will notarize a hallucination as faithfully as a fact. bind_artefact closes
that gap for the cases where you can point at a re-fetchable source: hash the actual
bytes the agent read and store that digest in the row, so a third party can
re-get the source and compare.
from arcaeon_ledger import Ledger, bind_artefact
log = Ledger("agent.log.jsonl")
art = bind_artefact("https://example.com/pricing") # or bytes, a file path, or a dict
log.append({"tool": "web.read", "url": "https://example.com/pricing", "artefact": art})
# art -> {"subject": {"name": "...", "digest": {"sha256": "..."}},
# "recipe": "sha256:raw-bytes:v1",
# "digest": "sha256:raw-bytes:v1:<hex>", "bound_at": "...", "source_meta": {...}}Digests are self-describing — never a bare hex hash. Each one is
sha256:<recipe>:<version>:<hex>, carrying its own recipe so a stranger reproduces
it from the string alone: raw-bytes:v1 (opaque bytes as-read) or json-c14n:v1
(a pinned, documented JSON canonicalization — sorted keys, compact, UTF-8). Recipes
are frozen and versioned append-only, so old rows keep their recipe forever and a
changed rule never makes history look tampered.
Verify honestly:
from arcaeon_ledger import verify_artefact
verify_artefact(art) # recipe reproducible + string self-consistent
verify_artefact(art, refetch=True) # for a URL: re-fetch and compare
# -> {"verdict": "live_match", # <- THE answer; read this field
# "digest_ok": True, "reason": None,
# "refetch": "match" | "mismatch" | "unavailable" | "skipped", "notes": [...]}Read verdict, not just digest_ok (0.5.7). digest_ok names only the
offline leg — recipe reproducible, string self-consistent — and it stays True
even when a live re-fetch disagrees. The top-level verdict tag mints the whole
answer in one field: "digest_consistent" (offline leg passed, no live comparison
made), "live_match", "live_mismatch" (live content no longer matches —
changed or tampered, indeterminate), "live_unavailable" (the requested live
check could not run), or the typed failure reason itself when the offline leg
fails. if out["digest_ok"] after refetch=True used to read green through a
live mismatch; out["verdict"] == "live_match" cannot.
A label this build cannot reproduce is a typed failure, never a pass. If the
digest names an algorithm, recipe, or recipe version outside the supported
registry, verify_artefact returns digest_ok=False with a machine-readable
reason — one of unknown_algorithm, unknown_recipe, unknown_recipe_version,
malformed_digest, subject_digest_mismatch — and never reaches the re-fetch
stage, so an unverifiable recipe can't come back as "match". A digest we cannot
recompute is a digest we did not check, and "did not check" must not be reported as
"verified." Old versions stay verifiable by staying listed in
SUPPORTED_RECIPE_VERSIONS when a new one is minted, so the append-only recipe
promise holds without the verifier waving through labels it has never shipped.
The honest boundary, stated loudly because it is the point: a re-fetch
mismatch means the content changed or was tampered — indeterminate. It is
never reported as proof of tampering. The web mutates, 404s, paywalls, and
personalizes; binding proves "this is the digest of the bytes the agent said it
read at time T," nothing stronger. For a neutral capture rather than your own
fetch, route the source through a notarizing snapshot; for existed-before-T, anchor
the digest externally. Each is a layer you add — stated, not implied.
The outside check: an external witness
The chain can't catch truncation alone — lop off the most recent rows and what
remains verifies clean (stated in "what it doesn't prove", above). The fix is a
witness: a record-keeper outside your own control that holds your head
(rows, chain) on a cadence. Once a witness has a pin from time T, a truncated
log has fewer rows than the witness saw, and a rewritten one has a different
chain at the witnessed row. Neither can hide.
from arcaeon_ledger import Ledger, WitnessStore, publish_head, verify_against_witness
log = Ledger("agent.log.jsonl")
witness = WitnessStore("witness_pins.jsonl") # ideally on a host you don't control
publish_head(witness, "billing-agent", log) # record the current head — do this on a cadence
# later — did the log survive intact?
v = verify_against_witness(witness, "billing-agent", log)
v.verdict # "consistent" | "truncated" | "rewritten" | "no_record" | "witness_broken" | "local_broken"
bool(v) # truthy ONLY on "consistent" — a missing pin is no_record, never a false ok
# READ THE VERDICT WITH ITS QUALIFIERS, never the bare string alone:
v.witness_self_integrity # "verified" | "unestablished" | "broken"A bare "consistent" is not the whole answer. The verdict also carries
witness_self_integrity: whether the witness store could prove its own pin
chain intact. A hosted client that only exposes latest() cannot self-verify,
so its verdicts read unestablished — the comparison ran honestly, but a
forged pin served by that store would compare clean. verified means the
store's own chain was recomputed; broken means it failed. A consumer that
branches on v.verdict == "consistent" without reading
witness_self_integrity is trusting the store's honesty exactly as much as it
would trust the log's — which is the arrangement a witness exists to replace.
(Found in the 2026-08-23 pre-invite audit, C14; the field exists so "not
checked" can never render as "checked and fine.")
WitnessStore is the reference witness: one append-only JSONL file of pins. A
hosted witness is a thin HTTP wrapper over exactly this object; run it locally
and you have a complete, offline, zero-cost witness you fully control (with the
obvious caveat that a witness you control is only as independent as its host).
What this proves, exactly. A witness proves your log wasn't truncated or rewritten only relative to what the witness saw, and only as recently as the last pin. Rows appended after the last pin are unprotected until the next one — so the MAX gap between pins is your real security parameter, not the average, because an attacker picks the gap. And it says nothing about whether the logged content was true — that's artefact-binding's job (above); the witness only guards the history's shape.
What the witness holds. Only fingerprints — (namespace, rows, chain, time) —
never your log content. Password-nowhere by design: if the witness is breached,
there is nothing sensitive to steal, only hashes useless without the original log.
Drop it into any MCP agent
arcaeon-ledger ships a zero-dependency MCP server, so any MCP client (Claude Code,
etc.) can give its agent tamper-evident logging with no code. Wire it in:
{
"mcpServers": {
"ledger": {
"command": "python",
"args": ["-m", "arcaeon_ledger.mcp_server", "--log", "agent.log.jsonl"]
}
}
}The agent then has five tools. Two are operator tools over one file:
ledger_append(record) to log an action (returns its chain hash) and
ledger_verify(strict?) to prove the log is intact (or get the exact tampered
line back). The verify verdict is three-valued, same as the library:
ok: true = every row verified, ok: null = chain intact but unchained
prechain rows were skipped unverified
(verified_scope: "bounded_prechain_skipped" — not a green), ok: false =
broken. Pass strict: true to make any unchained row a hard failure.
Three are agent tools (0.7.0), for when the output is going to somebody — a principal who wants proof, or a peer deciding whether to trust you:
tool | for | returns |
| log a batch of what you just did and hand your principal one hash |
|
| judge another agent's exported log from its text alone |
|
| your log broke — name it instead of re-minting a chain |
|
prove_my_conduct re-verifies after appending, so an agent whose ledger has
been tampered with gets chain_verified: false rather than a head hash with a
green attached. verify_peer_ledger returns first_break as an integer line
number (or null) so a calling agent can point at the exact bad row — it never
touches your ledger (the export is verified from a throwaway temp file, and the
call itself lands one row in <log>.calls.jsonl like every other tool call), and
an export with no parseable rows returns ok: null (verified_scope: "empty"),
because a green for sending nothing is the cheapest possible forgery. declare_break refuses when nothing is broken, and
never restores a green.
Agent ledgers live one file per namespace under --ns-dir (default ledgers/
beside --log). A namespace is a name, not a path: [A-Za-z0-9._-], traversal
refused rather than sanitized.
MCP is JSON-RPC over stdio and this server speaks it directly — no SDK, no extra install.
Status
Core library, CLI, and a drop-in MCP server, all tested: the library
against edit / delete / reorder tampering (test_ledger.py), the MCP server
through tools/list → append → verify at the request handler including
tamper detection, and the agent tools against namespace traversal,
peer-export tampering by exact line, and declared breaks (test_agent_tools.py). Extracted from a hash-chained action ledger
running in production. External anchoring ships via head() (publish the pin
yourself) and the reference witness (WitnessStore, above); a hosted witness
tier (retention, automatic pin cadence, compliance export) is the next layer.
MIT.
Available Tools
5 toolsdeclare_breakA
Your own ledger is broken — something was written into it out of band. Name the break instead of hiding it. This APPENDS (never edits) a row pinning the orphaned line's exact bytes, the reason, and the date, so a known break stops reading like an unexplained one. It does NOT restore a green: the verdict becomes bounded (ok=null) and the break stays counted in declared_breaks forever. It declares one break at a time — the first one verification already found — so a second break can never ride in on one sentence. Refused if nothing is actually broken.
| Name | Required | Description | Default |
|---|---|---|---|
| reason | Yes | A real human explanation of how the row got there. Blank is refused: an unexplained declaration is a mute exemption, not a record. | |
| namespace | Yes | The ledger to declare against — same name you pass to prove_my_conduct. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and does so thoroughly. It discloses append-only behavior, permanent retention, the effect on the verdict (ok=null), the one-break-at-a-time limit, and the refusal condition when nothing is broken.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is dense but every sentence earns its place: context, action, side effects, limits, and failure conditions are all covered without redundancy. The key behavior is front-loaded with the append-only clarification following immediately.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with no annotations and no output schema, the description is remarkably complete. It explains the semantics, permanence, refusal behavior, and constraints, leaving no critical gap that would prevent an agent from invoking it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description reinforces that a reason is required and references the ledger context, but it does not add materially new parameter-level meaning beyond what the schema already provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb and resource: declaring a break by appending a row to one's own ledger. It clearly distinguishes itself from siblings by limiting scope to the agent's own ledger and by explicitly describing the append semantics and refusal behavior.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It clearly implies when to use it: after verification has found a break, on the agent's own ledger, and only when something is actually broken. It does not explicitly name sibling alternatives, but the scope and refusal conditions provide enough guidance to select it correctly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ledger_appendA
Append one action record to a tamper-evident, hash-chained log. Returns the record's chain hash. Use this to log every consequential action (tool calls, payments, decisions) so the history can later be proven unaltered.
| Name | Required | Description | Default |
|---|---|---|---|
| record | Yes | Any JSON object describing the action (tool, args, result, actor, etc.). A `ts` timestamp is added if absent. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full behavioral disclosure burden. It reveals that the log is tamper-evident and hash-chained, that appending is the operation, and that the tool returns the record's chain hash. It could mention permanence or permission requirements more explicitly, but the core write behavior is transparent.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, with the primary action and return value front-loaded, followed by a concise usage directive. Every sentence contributes information and there is no redundant or filler text.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a single-parameter tool with full schema coverage, the description adequately covers what the tool does, when to use it, and what it returns. It does not explicitly mention the auto-added ts field, but that is already in the schema. Minor gaps such as error behavior or permissions do not make it insufficient.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the schema already documents that 'record' can be any JSON object describing the action, with a ts timestamp added if absent. The description adds no parameter-specific detail beyond calling it an 'action record,' so the schema carries the semantic weight.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description opens with 'Append one action record to a tamper-evident, hash-chained log,' which states a specific verb, resource, and effect. It also names the return value (chain hash) and clearly separates this append-side tool from verification siblings like ledger_verify and prove_my_conduct.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives explicit usage context: 'Use this to log every consequential action (tool calls, payments, decisions) so the history can later be proven unaltered.' It does not explicitly call out when not to use it or contrast it with alternatives, but the intended use case is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
ledger_verifyA
Verify the hash chain over the log. Returns a three-valued verdict: ok=true means EVERY row verified; ok=null means no break was found but unchained prechain rows were skipped UNVERIFIED (verified_scope='bounded_prechain_skipped' — treat as not-green); ok=false names the exact line of the first break (edit, deletion, reorder). Pass strict=true to make any unchained row a hard failure instead of a skip.
| Name | Required | Description | Default |
|---|---|---|---|
| strict | No | Treat ANY unchained row as a break (closes the fabricated-legacy-prepend hole). Default false: unchained rows before the first chained row are skipped, counted in `prechain`, and cap the verdict at ok=null. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full burden and handles it well. It discloses the three-valued verdict, the subtle ok=null case where prechain rows are skipped unverified, the verified_scope value, and exactly what strict=true changes. This is rich behavioral disclosure beyond the schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is two tightly packed sentences with no filler. The core action is front-loaded, and every clause earns its place by explaining verdict semantics, edge cases, or the strict flag.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given there is no output schema, the description fully covers return values, the three verdict states, the prechain edge case, and the strict parameter. The behavior is sufficiently complete for an agent to call and interpret the tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description largely repeats the strict parameter semantics already present in the schema, adding only the phrase 'hard failure instead of a skip.' This adds little meaning beyond what the schema already provides.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a clear verb and resource: 'Verify the hash chain over the log,' and goes on to detail the exact verdicts returned. It does not explicitly distinguish itself from the sibling tool verify_peer_ledger, although the focus on local 'log' semantics and prechain behavior provides reasonable differentiation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description gives clear context for when to use the tool—when hash-chain verification is needed—and explains the strict=true opt-in for stricter behavior. It does not explicitly name alternatives or state when not to use this tool over verify_peer_ledger, so it falls short of a 5.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
prove_my_conductA
Log a batch of things you just did to your own tamper-evident ledger, and get back one chain head you can hand your principal as proof. Returns {rows, head_hash, chain_verified}: head_hash is the current tip of the chain (give them this), rows is how many records stand behind it, and chain_verified is the three-valued verdict over your own log — true only if EVERY row verified, null if the scan was bounded (unchained or declared-broken rows; not a green), false if your log has been altered. Anyone holding an earlier head_hash can check that your history still contains it.
| Name | Required | Description | Default |
|---|---|---|---|
| events | Yes | One short line per action, in the order they happened. An empty list appends nothing and just reads back your current head. | |
| namespace | Yes | Your ledger's name, e.g. 'billing-agent'. One file per namespace. Letters, digits, dot, dash, underscore; not a path. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations provided, the description carries the full disclosure burden and largely meets it: it explains the three-valued chain_verified verdict (true only if EVERY row verified, null for bounded scans, false if altered) and the property that earlier head_hash holders can re-verify the history. It does not cover failure modes, irreversibility of writes, or authentication requirements, which keeps it just short of a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The purpose is front-loaded in one efficient sentence, and the dense middle content justifies itself by explaining a genuinely subtle three-valued return contract in the absence of an output schema. The only slight redundancy is stating the head is handed to the principal and later that earlier holders can check it — a minor overlap, not filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with no output schema and no annotations, the description fully specifies the return contract — the meaning of rows, head_hash, and each branch of the three-valued chain_verified — while the schema fully covers both parameters. The remaining gaps are the lack of explicit sibling-tool routing and unstated failure/authorization behavior, but the core calling contract is complete enough for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% and the schema already documents both parameters thoroughly: events gets ordering plus empty-list read-back semantics, and namespace gets character/path-safety constraints. The description adds no parameter-level meaning beyond the schema, so the baseline of 3 applies because the schema carries that burden.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The first sentence names a specific action ('Log a batch of things you just did') and a specific resource ('your own tamper-evident ledger'), plus the concrete deliverable ('one chain head you can hand your principal as proof'). This distinguishes it from siblings like verify_peer_ledger (which concerns peers) and declare_break (which marks broken rows). The verb, resource, and output are all explicit.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implicitly scopes the tool to 'your own' ledger, suggesting verify_peer_ledger for external ledgers, but it never names the alternative or states the selection condition. There is also no explicit guidance on when to reach for prove_my_conduct versus ledger_append for record-writing needs. The agent must infer the routing rather than being told.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
verify_peer_ledgerA
Another agent handed you its exported ledger as JSONL text — decide whether to trust it. Recomputes the hash chain over the text alone (no access to their machine, no writes on yours) and returns {ok, rows, first_break, declared_breaks}. first_break is the INTEGER LINE NUMBER of the first bad row (or null if none), so you can point at exactly where their history stops adding up. ok is three-valued: true = every row verified; null = nothing undeclared broke but the scan was bounded (unchained rows, an empty export, or breaks the peer DECLARED) — read verified_scope, and do not treat null as a pass; false = tampered. declared_breaks counts breaks the peer named and pinned itself, which is a mark of honesty, not of integrity: they are still breaks.
| Name | Required | Description | Default |
|---|---|---|---|
| strict | No | Treat ANY unchained row as a break, and ignore the peer's own declarations. Use when the peer claims a log chained from genesis. | |
| jsonl_text | Yes | The peer's whole exported ledger, one JSON object per line, verbatim. Do not reformat it — re-serializing changes the bytes the chain covers. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
With no annotations, the description carries the full burden and largely succeeds: it discloses zero side effects, no access to the peer's machine, and a three-valued ok result with an explicit warning not to treat null as a pass. It also explains first_break as a line number and nuances declared_breaks. The one flaw is referencing `verified_scope` without defining it, leaving a gap in an otherwise thorough behavioral disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is long (~180 words) but dense and front-loaded with the trigger scenario and core operation. Every sentence earns its place given there is no output schema to offload return-value semantics. The dangling reference to `verified_scope` is a minor structural wart, and the 'mark of honesty, not integrity' phrasing is slightly elaborate, but overall the length is justified by the complexity.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a tool with no annotations and no output schema, the description explains most of the return semantics (ok, first_break, declared_breaks) and side effects. However, `rows` is never described, and the description explicitly instructs the agent to read `verified_scope` without defining it — a real gap for an agent trying to interpret the result. Error behavior for malformed JSONL is also unaddressed, leaving the description mildly incomplete for a complex trust-decision tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description adds contextual reinforcement (verification operates 'over the text alone') but does not materially enrich the parameters beyond what the schema already says, particularly for strict, which the schema documents fully. No deduction, but no credit beyond baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb, resource, and scenario: decide whether to trust another agent's exported ledger as JSONL text by recomputing its hash chain. The framing ('no access to their machine, no writes on yours') implicitly but clearly distinguishes it from sibling tools like ledger_verify, which operate on one's own ledger, and ledger_append/declare_break, which mutate or annotate it.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The opening line gives a clear, concrete trigger condition: use this when another agent hands you its exported ledger and you must decide whether to trust it. It does not explicitly name alternatives or state when not to use it (e.g., 'use ledger_verify for your own ledger'), so it stops short of a 5, but the context is unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.
5 tool updates
v0.7.1- First observed
declare_break - First observed
ledger_append - First observed
ledger_verify - First observed
prove_my_conduct - First observed
verify_peer_ledger
TDQS
Each tool targets a distinct part of the ledger lifecycle, but verification-related tools overlap in behavior: ledger_verify, prove_my_conduct, and verify_peer_ledger all return three-valued chain verdicts. The descriptions are explicit about local vs peer vs proof use cases, so an agent can disambiguate with care.
Naming is mixed: ledger_append and ledger_verify use a noun_verb pattern, while prove_my_conduct, verify_peer_ledger, and declare_break use verb_noun patterns. All names are snake_case and readable, but the inconsistent structural convention makes the set less predictable.
Five tools is well-scoped for a specialized ledger server. Each tool covers a meaningful operation without unnecessary bloat, and the count fits comfortably within the expected range for a focused tool set.
The core append, verify, declare-break, proof, and peer-verification operations are present, but there is no tool to export or read the local ledger as JSONL, which verify_peer_ledger explicitly consumes. This is a notable gap that forces agents to obtain the ledger text through out-of-band means.
Maintenance
Related MCP Connectors
Issue & verify signed (ed25519), hash-chained, timestamped provenance receipts for agent actions.
Tamper-evident proof creation and verification for AI agents via MCP, A2A, and REST.
Hash-chained HMAC-signed audit log MCP for A2A (agent-to-agent) calls. Every tool-call, agent-ha...
Immutable event logging and audit trail for agent transactions
Related MCP Servers
- AlicenseAqualityCmaintenanceEvery agent action is recorded in a SHA-256 hash chain. Prove to clients that your agent did what it said it did. Record, query, verify, and export agent activity.3661MIT
- AlicenseNot gradedqualityFmaintenanceProvides tamper-proof audit logging for AI agents using SHA-256 hash chains, integrity verification, and compliance reporting for the EU AI Act.1MIT
- AlicenseNot gradedqualityCmaintenanceTamper-evident audit logging for AI agents. Append-only, hash-chained, optionally Ed25519-signed log. The MCP server lets an agent keep and verify a record of what it actually did.7MIT
- AlicenseNot gradedqualityCmaintenanceProvides an immutable, tamper-evident audit trail for AI agents, enabling event logging with cryptographic chaining, search, verification, and statistics.2MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/dan8433-user/ledger'
If you have feedback or need assistance with the MCP directory API, please join our Discord server