Agent Self-Reporting Is Not Evidence. Here Is What to Do About It.
Agent Self-Reporting Is Not Evidence. Here Is What to Do About It.
Your agent just ran send_email. It returned {"status": "sent", "to": "alice@company.com", "timestamp": "2026-04-04T14:03:12Z"}.
That response is a string produced by a tool running on a server you may not control. Between "agent invoked the tool" and "task complete", nothing independent confirms that the reported action happened, with the arguments you expected, at the time claimed.
This surfaces as real operational problems:
- A customer disputes an automated charge. Your agent logs say it happened. Their system says it didn't. Both are self-attested.
- A pipeline retries
store_recordafter a timeout. The agent reports one success. You can't tell which execution is canonical. - An auditor asks for evidence that action X preceded action Y. Your only proof is the system that executed both actions.
The common thread: agents self-report, and self-reports aren't evidence.
How MCP tool calls actually flow
your code (MCP client)
→ agent (Claude, GPT, Mistral...)
→ MCP server receives tools/call
→ tool function calls upstream API
→ upstream API returns response
→ MCP server returns result to agent
→ agent returns "Done."
Every step in this chain trusts the previous one. The agent trusts the tool's return value. You trust the agent's report. If the tool returned an optimistic response before the upstream actually processed the request, the agent doesn't know. Neither do you.
There's no independent observer in this chain. That's the gap.
Adding an independent witness
The fix is architectural: insert a neutral proxy between your MCP server and the upstream API. The proxy captures the exact request bytes, the exact response bytes, timestamps the exchange via an independent authority, and signs the record.
your code (MCP client)
→ agent
→ MCP server
→ neutral proxy ← captures + signs here
→ upstream API
→ receipt ID returned alongside response
The proxy doesn't execute business logic. It observes the HTTP exchange and produces a receipt — a signed record that exists independently of both your MCP server and the upstream API.
Implementation: server side (one helper function)
Here is a standard MCP server before and after adding receipts.
Before:
# your_mcp_server.py
import httpx
from mcp.server import Server
server = Server("my-tools")
@server.call_tool()
async def handle_tool(name: str, arguments: dict):
if name == "send_email":
resp = await httpx.post(
"https://mail-api.example.com/send",
json=arguments
)
return resp.json()
After:
import httpx
from mcp.server import Server
PROXY = "https://trust.arkforge.tech/v1/proxy"
API_KEY = "mcp_free_xxxx..." # 500 proofs/month, no card
server = Server("my-tools")
async def certified_call(target: str, payload: dict, tool: str) -> dict:
resp = await httpx.post(
PROXY,
headers={"X-Api-Key": API_KEY, "X-Agent-Identity": tool},
json={
"target": target,
"method": "POST",
"payload": payload,
"description": f"MCP tool call: {tool}",
},
timeout=30,
)
data = resp.json()
# data["proof"]["id"] → receipt ID, publicly verifiable
# Surface it in the tool response so the client can store it
result = data["response"]
result["_proof_id"] = data["proof"]["id"]
result["_proof_ts"] = data["proof"]["timestamp"]
return result
@server.call_tool()
async def handle_tool(name: str, arguments: dict):
if name == "send_email":
return await certified_call(
"https://mail-api.example.com/send", arguments, "send_email"
)
One function. One extra line per tool. The upstream API call works exactly as before — the proxy forwards it transparently. The difference: every call now produces a signed, timestamped receipt.
What a receipt contains
Each receipt bundles five fields:
| Field | Content |
|---|---|
request_hash |
SHA-256 of the exact payload sent to the upstream API |
response_hash |
SHA-256 of the exact response received |
timestamp |
RFC 3161 timestamp from an independent Timestamp Authority |
signature |
Ed25519 signature, verifiable with the proxy's public key |
rekor_log_id |
Entry in Sigstore Rekor, a public append-only transparency log |
Three independent witnesses: the proxy's Ed25519 signature, an external TSA, and a public transparency log. No single party can forge or alter the record without the others detecting it.
Implementation: client side (verification)
The server-side change generates receipts. The client-side code lets you verify them independently — without the MCP server's cooperation.
import httpx
import hashlib
import json
PROOF_BASE = "https://trust.arkforge.tech/v1/proof"
def canonical_json(data: dict) -> str:
return json.dumps(data, sort_keys=True, separators=(",", ":"))
def verify_receipt(proof_id: str, original_payload: dict) -> dict:
"""
Verify a receipt against what you originally sent.
No auth required — verification is always free.
"""
# 1. Check receipt integrity (signature + transparency log)
check = httpx.get(f"{PROOF_BASE}/{proof_id}/verify").json()
if not check.get("integrity_verified"):
return {"valid": False, "reason": "integrity check failed"}
# 2. Compare payload hash — was this the request I actually sent?
proof = httpx.get(f"{PROOF_BASE}/{proof_id}").json()
recorded = proof["hashes"]["request"].replace("sha256:", "")
expected = hashlib.sha256(
canonical_json(original_payload).encode()
).hexdigest()
return {
"valid": recorded == expected,
"timestamp": check.get("timestamp"),
"rekor_status": check.get("transparency_log", {}).get("status"),
"verification_url": check.get("verification_url"),
}
Call verify_receipt from anywhere — your CI pipeline, a monitoring job, an audit script. The proof endpoints are public. You can verify a receipt months after the original action.
Practical example: dispute resolution
Your agent sent an email on behalf of a customer. The customer claims they never received it. Here's the resolution workflow:
async def investigate_disputed_email(proof_id: str, original_args: dict):
result = verify_receipt(proof_id, original_args)
if not result["valid"]:
# Receipt doesn't match what we think we sent
# → investigate server-side issue
return {"finding": "payload mismatch", "detail": result}
# Receipt is valid: we can prove the exact request was sent
# and the exact response received, at a certified time
return {
"finding": "verified",
"sent_at": result["timestamp"],
"transparency_log": result["rekor_status"],
"shareable_proof": result["verification_url"],
# → share this URL with the customer or their support team
}
The verification_url points to a public HTML page with a human-readable breakdown and color-coded verification badge. No login required. Share it in a support ticket, a compliance report, or a Slack thread.
When receipts are worth the overhead
Each receipt adds one HTTP round-trip. That's measurable latency. Use receipts selectively:
Worth it:
- Irreversible actions (email sends, payment initiations, record deletions)
- Cross-party handoffs (output consumed by another team or organization)
- Compliance-sensitive operations (regulated industries, audit requirements)
- Multi-agent chains (tracing causality across delegation boundaries)
Skip it:
- Read-only queries (search, lookups, summaries)
- Idempotent operations (safe to retry without side effects)
- Internal-only actions with no dispute potential
What receipts don't prove
Receipts prove transport-layer facts: the exact bytes sent, the exact bytes received, the certified time. They don't prove:
- That the upstream service processed the request correctly (a mail API could accept a request and silently drop it)
- That the agent chose the right action semantically
- That the tool's return value was truthful
For semantic correctness — did the agent do the right thing, not just a thing — you need application-level checks. Receipts eliminate the "did it happen?" question so you can focus on "should it have happened?"
Getting started
1. Get a free API key (no card, 500 proofs/month):
curl -X POST https://trust.arkforge.tech/v1/keys/free-signup \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com"}'
2. Add certified_call to your MCP server (code above — one function, one line per tool)
3. Store proof IDs client-side alongside your action records
4. Verify on demand:
curl https://trust.arkforge.tech/v1/proof/prf_20260404_140312_a8c3f1/verify
Verification is always free, regardless of plan. The proof exists independently of both your infrastructure and ours — the Sigstore Rekor entry is the third-party anchor.
ArkForge Trust Layer is built around this requirement: provider-agnostic verification that works across any model, any MCP server, any upstream API. Free tier: 500 proofs/month. Pro starts at €29/month for 5,000 proofs. Full pricing | GitHub | Live API
Prove it happened. Cryptographically.
ArkForge generates independent, verifiable proofs for every API call your agents make. Free tier included.
Get my free API key → See pricing