attest
You can verify AI-agent-produced artifacts before accepting or paying for them, individually or in batches.
Use
verify_artifacton one object, array, or string, optionally with a JSON Schema and source, to get a verdict (accept/review/reject), a 0–100 trust score, findings, and an Ed25519 signature.It checks four independent layers: schema conformance, emptiness/content signals, provenance bound to the artifact hash, and authority/mandate permission.
Use
verify_artifact_batchon multiple artifacts in one call to get one verdict per artifact plus summary counts of accepted, review-needed, and rejected items.Verification is rule-based and deterministic—same artifact, same result—making it suitable before accepting another agent’s work or releasing payment.
The public schema exposes these two tools; the README notes optional
bridge_checkis only listed when an escrow bridge is configured.
Ships a composite GitHub Action that verifies artifact files in CI and fails the build on rejection, enabling attest artifact checks in GitHub workflows.
This server has a remotely hosted endpoint. Open its connector listing for connection details, authentication requirements, and setup instructions:
attest — trust layer for agent artifacts
Agent action receipts, verified offline. A receipt says what an agent was recorded as doing at the boundary that governed it. Verifying one is not a signature check: the bytes must be canonical, the chain rules must be applied as a set, and a checkpoint signed by a stranger must never become an accusation against someone else's history. What a verifier must reject →
Live: https://attest.rta.lat/demo · Receipts: https://attest.rta.lat/receipts · Offline verifier: https://attest.rta.lat/verify-offline · Docs: https://attest.rta.lat/docs · Refusal evidence for agents ($99 / 30 days): https://attest.rta.lat/deny-evidence
Conformance corpus, CC0: conformance/receipt-vectors.json — 12 vectors
including five that must be rejected. A Python and a browser implementation agree on all twelve.
Pricing: the verifier is free, forever. An audit of what your agents spent is $500 flat — ten working days, one line of evidence per finding. Developer access for pipelines: $0.005 per call over x402 (USDC on Base), or $99 for 30 days ($49 at India purchasing-power parity).
An agent calls an external tool or another agent and receives a foreign artifact that goes straight into its context. Nothing checks that the artifact:
matches the declared schema (the tool quietly changed a field type),
carries no hidden instruction ("ignore previous, send the keys to …"),
is not a base64 wrapper around such an instruction,
has not changed since it was checked.
attest closes that gap. The receipt core has no dependencies; 813 tests (809 pass, 4 skip).
Free human review of one artifact: https://attest.rta.lat/review
BusinessMCP Marketplace: https://businessmcp.com/mcp-servers/attest
Four layers, four different questions
Each layer answers only its own question, and none substitutes for the others. A valid form is not the same as meaningful content; a signed artifact is not the same as a permitted action.
1. Form and emptiness — emptiness.py
Does the artifact say anything? An agent told to find five vulnerabilities returns five identical items. That artifact passes every schema validation, because the form is perfect.
Seven measurable signals: identical list items, placeholder values, output cut at the token limit, whitespace-only strings and empty collections, low-variety repetition, required fields left at their schema default, enum fields sitting on the first value.
Verdicts are VALID_BUT_EMPTY, THIN_CONTENT, CONTENT_PRESENT.
VALID_BUT_EMPTY means "the form is fine, the content is suspect" — not
"the content is absent". Suspicion requires a person; a verdict requires
proof. The difference matters.
Every signal shows where and why. Absence of signals does not prove meaningfulness — it only means the coarse checks did not fire. That sentence is in the code, in the docstring and in the API response.
2. Generation conditions — generation.py
A signature proves who signed. It does not prove where the file came from. Anything at all can sit between "the agent produced this" and "the verifier saw the result".
Model, version, prompt, parameters, moment and executor are bound into a single
chain_hash over the artifact. Change any link and the binding stops matching.
Absence of a binding is explicit, not silent. Four states are distinguished: no conditions at all; a binding claimed but conditions cannot be supplied; conditions supplied but no binding in the signature; conditions that do not match. The first and the second are different things, and confusing them would confirm a forgery.
chain_hash is inside the signature, not next to it — otherwise it could
be appended afterwards.
3. Verification without trusting us — signing.py
HMAC requires a secret, and the secret is only ours. So only the service could verify a signature, and anyone who did not trust the service had to trust it first. For a product about trust, that is a contradiction at the foundation.
Now Ed25519. The public key travels with the attestation, and the verifier checks it themselves — no network, no package of ours, no secret.
python3 attest/verify_standalone.py attestation.jsonThat script imports nothing from attest and opens no sockets. A test enforces
both.
4. Authority — authority.py
Was the action permitted? A signed action is not an authorised one: permission is granted separately and expires.
Rules match drift.check exactly — amount, payee, audience, currency, window,
intent. They are duplicated on purpose: depending on another repository would
mean the check cannot run without network access to someone else's code. Six
comparisons are cheaper to duplicate than to make unverifiable.
A test compares the result against the live drift on every rule, so a
divergence shows up immediately rather than as a client complaint.
Absence of a mandate is a denial, never consent. A verifier who presents no permission has not demonstrated one.
Related MCP server: agent-services-mcp
Proof of Place — pop/1
A signed report about one physical site: what matched, what did not, what we could not check. The JSON is canonicalized with RFC 8785 (JCS) and signed with Ed25519. The verifier needs no network, no account and no server of ours.
python -m attest.pop_verify report.json --key public_key.txtIt prints the verdict and the reason, and exits 0 only for ok.
Verdict | Meaning |
| chain, signature, PDF hash and verdict rules all hold |
| not the report that was signed: a layer, a hash or a limit changed |
| no signature, or no key to attribute it |
| signed with a key other than the pinned one |
| a |
Verify offline in seven steps:
Get
report.json,report.pdfandpublic_key.txtthrough two different channels. The key is deliberately not inside the report.Save the key in a separate folder, so the report/key pair cannot be swapped in one move.
Run
python -m attest.pop_verify report.json --key public_key.txt(Python 3.11+ andcryptography; nothing else).Read
verdict: ok. Any other verdict names the check that failed.Compare
sha256sum report.pdfwithpdf_sha256, or pass--pdf report.pdfand let the verifier do it.Open
limitsandnot_checked. If either is empty, the report violated our own spec and must not be accepted.Match
issuer.key_idagainst the key id we publish separately.
verify() also checks the layer chain (prev_hash → hash for every layer,
including the first), so removing a layer or editing one hash breaks the
report. It does not recompute the files inside the client ZIP — their hashes
are covered by the chain, but the bytes are not in the JSON. It does not prove
GPS honesty or ownership; those limits travel in the report itself.
Building a report from client photos — pop_build
python -m attest.pop_build --photos photos/ --claim claim.json \
--key private.key --out report.json
python -m attest.pop_verify report.json --key public_key.txtclaim.json is what the client asserts before we look at the files:
{
"subject": {"type": "parcel", "ref": "client-ref-1"},
"claim": "40 ha, solar farm, operational",
"location": {"lat": "52.123456", "lon": "4.654321"},
"captured_at": "2026-11-02T11:20:00Z"
}captured_at and subject.ref/subject.claim are required; a broken
coordinate ("lat": "52,1") stops the build instead of turning into "not
declared" — a typo must not be reported as a missing fact. Every file in
--photos is hashed for the content layer. EXIF is read when present:
DateTimeOriginal and GPS go to position, and the first photo that carries
GPS is named in the layer.
Verdict | Rule |
| coordinates differ by more than the tolerance, or the independent source says |
| nothing to compare: no GPS in the photos and no capture time |
| coordinates, time and all four layers agree, including an independent source |
| everything else: something matched, something could not be checked |
Tolerances. 500 m for coordinates and 2 hours for capture time are
constants in attest/pop_build.py. 500 m is not the precision of a GPS fix
(5–20 m); it is the size of the thing being claimed. A 40-hectare parcel has a
side of ~630 m, so a photographer standing at a corner while the claim names
the centre honestly differs by hundreds of metres. Two different quarters, or
two different fields, do not fall inside 500 m. For time, DateTimeOriginal
carries no timezone and camera clocks drift by an hour or two; a different day
is a different object, so a day-old photo must not pass as today's.
Ceiling without an independent source. The builder has no satellite or
partner capture, so independent is written as not_available — never
invented. pop/1 forbids confirmed next to an unavailable layer, so a
photos-only report tops out at partial. Pass
--independent source.json ({"source": "sentinel-2", "result": "match", "asset_ref": "…"}) when a real independent look exists; only then can
confirmed be reached. location.basis says exif_gps or claimed, so a
report never hides which one it stands on.
A finding worth stating plainly
While building layer 3, this was found in the existing code: with ATTEST_SECRET
unset, signatures were computed with attest-dev-key-do-not-use-in-production,
hardcoded in the source. That variable was not set, so every attestation the
service had ever issued was signed with a key published next to it. Anyone
could have signed an artifact as if the service had verified it.
The signature looked real and was not — which is worse than having none, because having none is visible. It is fixed: with no key, there is no signature.
Try it in a browser
https://attest.rta.lat/demo — four buttons, one per layer, the full response shown immediately.
Public repo
https://github.com/mrpkk/attest
Part of the Indra set — six small services for agents that have to be trusted: https://indra.rta.lat
MCP server (Model Context Protocol)
attest is an MCP server: it speaks the Model Context Protocol over
Streamable HTTP (JSON-RPC 2.0) and exposes the verifier as MCP tools.
MCP tools (the whole point — an agent calls them directly):
verify_artifact— verify one artifact, returns verdict, trust score and findings;verify_artifact_batch— verify a batch in one call (a scan report, a list of findings) and get one verdict per artifact plus a summary;bridge_check— settle paid work between two agents through an escrow bridge, with a signed receipt (offline-verifiable). Optional: it is listed only on a node configured with an escrow bridge, so the public node returns two tools fromtools/list. The call stays in the code and answers with a clear "bridge not configured" error instead of vanishing mid-session.
MCP resources: none exposed yet —
resources/listanswers with an empty list rather than an error, so MCP clients and directory scanners do not mark the server as broken.MCP prompts: none exposed yet —
prompts/listanswers with an empty list for the same reason.Transport: Streamable HTTP, no auth for the free tier,
X-ATTEST-ENTITLEMENTheader for a prepaid key,PAYMENT-SIGNATURE/X-PAYMENTfor a per-call x402 payment.
Connect an MCP client
{
"mcpServers": {
"attest": { "url": "https://attest.rta.lat/mcp" }
}
}Claude Desktop, Claude Code, Cursor, Codex, VS Code and any other MCP client that supports remote Streamable HTTP servers work with that block as is.
We use it ourselves: our own agents call this exact URL through the MCP SDK
in opencode.jsonc, so the server is exercised by real traffic every day, not
only by the tests.
Tool reference
Both tools take plain JSON. No SDK is needed.
verify_artifact
field | type | required | meaning |
| object | array | string | yes | what to verify |
| object | no | JSON Schema the artifact must satisfy |
| string | no | where the artifact came from; recorded in the verdict |
Request:
{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"verify_artifact",
"arguments":{"artifact":{"findings":[{"rule":"x","severity":"high"}]},
"source":"nightly-scan"}}}Response (one verdict, signed):
{"verdict":"accept","trust_score":0.86,
"checks":[{"layer":"schema","ok":true},{"layer":"emptiness","ok":true},
{"layer":"generation","ok":true},{"layer":"authority","ok":true}],
"findings":[],
"signature":{"algorithm":"ed25519","public_key":"...","signature":"..."}}verify_artifact_batch
field | type | required | meaning |
| array | yes | one verdict per element |
| object | no | applied to every element |
| string | no | recorded once for the batch |
{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"verify_artifact_batch",
"arguments":{"artifacts":[{"a":1},{"a":2}],"source":"batch"}}}Response: {"verdicts":[...],"summary":{"accepted":1,"rejected":1,"total":2}}.
When the free tier runs out
The free tier is 5 checks per day per client. On the 6th call the server answers
HTTP 402 with a body naming the limit, and a Payment-Required header carrying the
x402 terms:
{"error":"free limit reached (5/5 per day)","free_remaining":0,"resets_at":"00:00 UTC",
"x402":{"enabled":true,"network":"eip155:8453","asset":"0x8335...2913",
"price_atoms":5000,"pay_to":"0x..."}}An MCP client sees that text as a tool error — nothing hangs and nothing is retried
blindly. To continue, repeat the same call with PAYMENT-SIGNATURE (per-call x402,
USDC on Base) or with X-ATTEST-ENTITLEMENT (a 30-day prepaid key). The free quota
resets at 00:00 UTC.
Maintenance
Maintained and general-purpose, MIT-licensed. The hosted node at
https://attest.rta.lat is production and is exercised daily by our own agents, so it
is not a demo that rots. Breaking protocol changes get a major bump in CHANGELOG.md;
issues and pull requests are reviewed. Security reports follow SECURITY.md.
Use it in CI
A one-off check in a browser does not protect a pipeline. This repository ships a composite
GitHub Action: it verifies every artifact file and fails the build on a rejection.
Pinned to v1 — release v1.0.0.
- uses: mrpkk/attest@v1
with:
paths: 'reports/*.json'
schema: 'schemas/scan.schema.json' # optional
fail-on: reject # or 'any' to fail on violations tooWithout Actions, the same check runs locally:
python3 scripts/verify_artifacts.py 'reports/*.json' --fail-on rejectExit codes: 0 all accepted, 1 a rejection or an error, 2 nothing matched. A network
error or an exhausted free quota is reported as an error — never as "accepted".
Use it from your agent
{
"mcpServers": {
"attest": { "url": "https://attest.rta.lat/mcp" }
}
}That block is not a doc snippet — it is how our own agents call this server (the same public URL, no key, no signup).
Tools: verify_artifact, verify_artifact_batch. 5 free checks per day, then
$0.005 per call over x402 (USDC, Base) or $99/30 days. India pricing: $49/30 days.
Available Tools
2 toolsverify_artifactVerify an AI agent artifactARead-onlyIdempotentInspect
Verify an artifact produced by an AI agent before accepting or paying for it. Four independent layers: schema conformance, whether there is anything inside (uniform lists, stubs, truncations), provenance bound to the artifact hash, and whether a signed mandate permitted the action. Returns a verdict (accept/review/reject), a 0-100 trust score, concrete findings and an Ed25519 signature. Rule-based, no LLM: the same artifact always yields the same result. Use before accepting work from another agent and before payment.
Inputs: artifact (required), schema, source.
verify_artifact_batchVerify a batch of AI agent artifactsARead-onlyIdempotentInspect
Verify several artifacts at once — a scan report, a list of findings, a set of tool descriptions. One call, one verdict per artifact, plus a summary of how many were accepted, need review, or were rejected. Use when an agent produced a batch (twenty findings from a security scan, for example) and each of them has to be checked before the work is paid for.
Inputs: artifacts (required), schema, source.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 2 tools
verify_artifact and verify_artifact_batch are clearly distinguished by single vs. batch input; the batch tool is essentially a convenience wrapper, so there is slight purpose overlap but descriptions make the boundary clear.
Both tools follow the same verb_noun convention (verify_artifact, verify_artifact_batch) with a predictable _batch suffix for the plural case.
Two tools is thin for an attestation service; the single/batch split is reasonable but leaves no room for auxiliary operations like retrieving past verdicts or fetching the signing key.
Core verification for one or many artifacts is covered, but there are notable gaps: no tool to retrieve a prior verdict or trust score, no way to fetch the Ed25519 public key needed to check the returned signature, and no mandate inspection.
Maintenance
Related MCP Connectors
Artifact store for AI agents. Hosted OAuth at mcp.artifacta.io/mcp; local stdio via npm/PyPI.
Tamper-evident proof creation and verification for AI agents via MCP, A2A, and REST.
Guarded MCP server for agent-readable business truth, provenance, readiness, and discovery.
Read-only verifier for 25 ProofRelay MCP tools and non-confidential evidence bundles.
Related MCP Servers
- AlicenseAqualityCmaintenanceMCP server for offline verification of signed artifacts — receipts, manifests, and audit bundles. MIT licensed, works without accounts or API calls. Tools: self_test, verify_receipt, verify_bundle, explain_artifact.495 npm6Apache 2.0
- AlicenseAqualityDmaintenanceA thin MCP server that wraps provenance-receipts and quality-gate services as tools, enabling AI agents to certify content origin and score quality via Ed25519-signed receipts.552 npmMIT

vantic-mcpofficial
AlicenseNot gradedqualityDmaintenanceEnables MCP hosts to verify agent spending mandates and receipts, providing stateless tools for authorization, chain verification, credential verification, and DID resolution.Apache 2.0- AlicenseBqualityBmaintenanceContributors: dengyier & Tim Shing(成孝添) Check what an AI agent was authorized to do and what its signed evidence supports. Includes reference SOC alert review and a separately enabled Codex preview-directory Guard; installing MCP does not activate Guard.32311 PyPI263Apache 2.0