04 · Audit trails

A tamper-evident audit trail for every MCP tool call.

Ed25519-signed, externally verifiable, no client opt-in required. Open-source, donated to DIF. One wrapper, one verification call.

Three months after an agent makes a tool call on your MCP server, a customer disputes the result. Compliance asks for the audit log. Your regulator wants the chain of custody. Opposing counsel subpoenas the records. What you have is a SQL row. What they want is proof — that the call happened, that nothing has been altered since, that the row matches what was actually returned at the time. You cannot give them that. The same code that processed the call also wrote the row, and there is no internal check between the two.

A signed receipt closes that gap. Every response your server emits gets a small cryptographic receipt — Ed25519-signed, canonical-hashed, audience-bound — riding in a field your existing clients already ignore. Your auditor verifies a receipt with a single function call against your public DID. No database access required. The marginal cost on top of running an MCP server is one library wrapper, an Ed25519 keypair, and a published DID document. You do not need a single client to upgrade.

Tamper-evident is a marketing word when one party owns both the action and the record. It becomes a property when the record is signed at the moment the action is taken.

Three steps, on your existing server

If you already run an MCP server, the integration is one install, one wrapper, and one verification call. Copy and run.

npm install @kya-os/mcp

On the server, one wrapper around your existing MCP instance:

import { withKyaOs } from "@kya-os/mcp";

const server = withKyaOs(myMcpServer, {
  did:    "did:web:my-server.example.com",
  signer: myEd25519Signer,
});

From any client — the original requester, an auditor three months later, opposing counsel — one verification call:

import { verify } from "@kya-os/mcp";

const verdict = await verify(response.result._meta.proof, {
  expectedAudience: "did:web:my-client.example.com",
  expectedRequest: request,
  expectedResult:  response.result.data,
});

That is the whole integration. Open MCP Inspector against your wrapped server, call any tool, and you will see _meta.proof on every response — a receipt your auditor can verify in one function call.

Fig. 01Repo in MCP Inspector → call tool → result + _meta.proof on every response

What follows is what a receipt looks like on the wire, how anyone outside your trust boundary can verify one, why deploying receipts does not require coordinating the rest of the ecosystem, and the specific Monday-morning moves for a server operator and a client builder.

What a receipt looks like on the wire

A receipt is an Ed25519 signature over a canonical hash (RFC 8785) of the call — method, params, and result.data — audience-bound to your server's DID and scoped by a per-call nonce. It rides MCP's _meta field on every tool response:

{
  "jsonrpc": "2.0",
  "id": "req-9f3a",
  "result": {
    "data": { "tx": "tx_01HW...", "status": "settled" },
    "_meta": { "proof": "eyJhbGc..._.detached..signature" }
  }
}

That is the entire over-the-wire change. No new HTTP header. No transport variant. No protocol version bump. _meta is the spec-mandated extension surface for tool-server-defined metadata, and the spec is explicit that clients must tolerate unknown keys. Existing clients walk result.data for their work and never read _meta. Nothing breaks.

MCP Inspector · tools/call · payments.charge
Fig. 02What MCP Inspector shows for a wrapped tool call · _meta.proof rides on every tools/call response

Verifiable from the outside

Verification is a pure function: receipt, resolved public key, expected audience. It does not phone home. It does not require an account. The verdict is the same whether the verifier is the original client, an auditor a year later, or opposing counsel — anyone with the receipt and your public DID document can reproduce it.

That property is the whole point. The audit trail you retain is portable evidence — verifiable outside your trust boundary, in a JOSE format every standards-aware regulator already recognizes. You hand over the receipt; they run one function; they get a verdict. That is materially easier to attest to in a SOC 2, ISO 27001, PCI, or SOX-adjacent context than "here is a CSV export of our Postgres audit table, trust us."

The reference implementation is @kya-os/mcp — open-source, donated to the Decentralized Identity Foundation. That last part matters more than it sounds. The protocol is governed by a neutral standards body, not a vendor; the wire format is JOSE; identity resolution is W3C DIDs; canonicalization is RFC 8785. Every layer below the receipt is a standard a procurement officer can google, and the implementation lives in an org that cannot be acquired or paywalled out from under you.

Beyond signed receipts: tools that ask permission first

The same library that signs every response also gates write-scope tools behind a verifiable credential. Tag a tool with the scope it needs — cart:write, payments.execute, db.delete — and a call that arrives without a matching delegation comes back not with a result but with a consent URL. The user approves in a separate channel, the server issues a VC scoped to that one tool with an explicit expiry, and the retry clears with the delegation and the proof riding side-by-side in _meta.

That is the checkout step in the demo above: blocked first time, retried after the consent flow, and the second response carries the credential alongside the receipt. The audit table now answers more than "did this happen?" It answers "who delegated what to whom, scoped to which tool, expiring when?" For regulated verticals that turns the audit trail from a forensic artifact into a live access-control surface — same primitive, both jobs, no second system to operate.

Each side accrues value alone

A regulated server operator who deploys receipts and whose clients never read _meta still gets most of the value. Any later mutation of a stored row — by a DBA, a migration script, a compromised admin, a subpoena-driven redaction — becomes detectable the moment anyone runs verify. The receipt is its own integrity check, dormant until called on. Tamper-evident by construction, not tamper-evident by process.

A regulated-vertical client that calls verify on every response — even if no peer client is doing the same — owns non-repudiable evidence the server cannot disown. The request it sent, the response it got, hashed canonically, signed under the server's DID. In a dispute, the client hands the receipt to a third party who runs verify and gets a verdict.

Adoption is not gated on consensus. Each side accrues value the moment it ships.

The asymmetry does not fix the median case. Most generic agent frameworks — LangChain, LlamaIndex, Cursor's MCP client, the long tail — will not pick this up until a regulator mandates it, a procurement contract requires it, or a default lands in a major SDK. That is fine. The asymmetry says you do not have to wait.

Ship this week

Two reader groups, two concrete moves.

01If you operate a regulated MCP server

npm install @kya-os/mcp

Wrap your server, then publish your DID document at a stable URL (did:web resolves to https://your-host/.well-known/did.json) and configure a nonce store and key-rotation policy you can defend to your auditor:

const server = withKyaOs(myMcpServer, { signer, did });

Open MCP Inspector against the wrapped server and confirm _meta.proof appears on every tools/call response. The signing path itself is cheap — a day's work; the project is the operational chrome around it (key rotation, nonce store, attestation runbook the auditor signs off on). What you ship is a tamper-evident-by-construction audit trail in a JOSE format every standards-aware regulator will recognize.

02If you ship a client into a regulated vertical

npm install @kya-os/mcp

Call verify on every response from a server that emits receipts:

const verdict = await verify(response.result._meta.proof, {
  expectedAudience,
  expectedRequest,
  expectedResult,
});

Persist the verdict and the receipt alongside your existing logs — the receipt is the artifact your auditor will want, not the log line. You now retain portable evidence the server cannot disown.

Each move makes the next one cheaper. Neither requires the other.