Proposal: Auditable MCP: tool-internal audit events sealed into a host-side tamper-evident ledger #3112
Replies: 12 comments 1 reply
|
BTW, I've also published the reference SDKs (Python/TS) that implement this. SatoshiImai/auditable-mcp-sdk-ts |
|
Two things security reviewers ask about, which might be worth stating explicitly in the proposal:
|
|
This is close to something we built independently, so a few notes from the same trench — and @grabdoc's first question is the one I'd most encourage you to answer explicitly in the proposal, because we got it wrong at first. On "can a tool revise what it already declared": a hash chain proves internal consistency — that this sequence hasn't been edited since it was written. It does not prove authorship, and an actor holding the whole log can rewrite every link from scratch and present a perfectly valid chain. That distinction matters in a regulated setting: an auditor asking "could the operator have fabricated this?" is not answered by chain validity alone. What closes it is a signature from a key the operator doesn't hold, or an external anchor (a periodic head-hash published somewhere they don't control). Host-side sealing is a real step toward that — it moves the pen out of the tool's hand — but it's worth stating plainly which threat it does and doesn't cover, because "tamper-evident" gets read as "tamper-proof" by exactly the people who need the difference. On cross-language byte-identical chains: we do the same (RFC 8785, Python + TypeScript, vectors generated by the Python side and asserted on every commit), and the sharpest edge we hit was numbers. JCS defers to ECMAScript On composing records across vendors (@grabdoc's second question): I don't have a good answer, and I suspect it's the harder half. Chains from three tools compose into one timeline only if they agree on identity — what makes two records refer to the same operation. In our work that question (what makes two calls "the same call") turned out to be domain-specific and the easiest thing to get wrong: too narrow gives false duplicates; too wide collapses two legitimately different operations into one, which looks like success and is far worse. A shared record format doesn't settle it by itself. One field worth considering: whether the effect was confirmed by the system of record, kept strictly distinct from "we believe we did it". In audit terms "we sent it" and "the provider confirms it exists" are different claims, and collapsing them is how a log ends up attesting to something that never landed. Ours is at https://github.com/aurumflux20/once-kernel-ts (Apache-2.0, |
|
Hi @SatoshiImai , Really glad to see this. I was putting together a proposal for essentially the same problem and found this SEP already in flight. Saved me from filing a duplicate. I built something that might be useful here. It's an intermediary that produces tamper-evident records transparently for any MCP server without modifying it (https://github.com/elang2/mcp-audit-gateway). Wraps via stdio, intercepts Reading through the SEP, I think the deployment model is the piece that's complementary to what you've specified. You've nailed the record format and verification. What I kept running into while building the gateway was the "who holds the signing key" question — putting it in an operator-controlled intermediary means the tool server can't forge and the client can't suppress. Might be worth a note in the SEP about where in the stack record production happens, even if it stays non-normative. One thing I'd want to align on: my canonicalization uses a fixed-order tuple array rather than sorted-JSON. Different tradeoffs but same goal. I'd rather converge on whatever this SEP lands on than have two incompatible forms out there. Once the format stabilizes I'll run the conformance vectors against my implementation. Is there anything specific you'd find useful from the gateway side? Happy to contribute. |
This comment was marked as spam.
This comment was marked as spam.
|
@eason4kim-rocket point 2 is the one I'd most want the SEP to pin down, because it hides a distinction that's easy to miss and expensive to get wrong — the same failure the seal is meant to prevent, one layer down. "Record an unverified_outage event" is right, but outage is really two different ledger states:
So the honest ledger needs a third state that survives the outage: It's not theoretical for the reservation case you raise: releasing on an ambiguous timeout is precisely how a spend cap loosens on headroom that was never real. Erring toward "held" until reconciled keeps the cap honest — it can only ever be too tight, never too loose. On @elang2's canonicalization note — converging matters more than which form wins, and the place two implementations most often silently disagree is number encoding, not key ordering. JCS defers to ECMAScript |
This comment was marked as spam.
This comment was marked as spam.
|
@eason4kim-rocket Yeah the safe integer point is a good one. The vectors I put up already stay within those bounds (only numeric fields are |
This comment was marked as spam.
This comment was marked as spam.
|
@eason4kim-rocket Good timing on the comparative diff. A few things to know before you run: Pin to the v0.4.0 tag (https://github.com/elang2/mcp-audit-gateway/tree/v0.4.0/test/vectors) rather than main. The vectors stabilized this week after the in-toto attestation predicate review (in-toto/attestation#588) caught a couple of things that tightened the design. The main one that'll affect your conformance run: chain_break successors chain from the break record's own hash, not a fresh "genesis." If your pipeline resets to a genesis state after a discontinuity, the vectors will fail you there. The reasoning is that chaining from the break hash makes the scar undeletable. Remove the break record and linkage breaks. It's a stronger guarantee than restart-from-zero. The canonicalization split you validated is now explicitly scoped in the vectors: Looking forward to seeing where the diff diverges. That's exactly the kind of cross-implementation signal that tells you whether the spec is tight enough or still ambiguous somewhere. |
|
@eason4kim-rocket Heads up before you run against v0.4.0. I shipped v0.6.0 today and it's a better baseline for your conformance test. What changed: I ran differential tests across all 10 official MCP SDKs and found 26 wire-level serialization divergences that directly affect hash-chain implementations. Six SDKs produce six different wire representations of I tightened the canonicalization in v0.6.0 because of all this. Integers only, so the float formatting problem goes away entirely. Explicit field order instead of relying on sort, since three SDKs sort three different ways. Surrogate rejection at the boundary. None of the 26 divergences can affect the tuple-array form. If your pipeline uses any of these SDKs, the divergence table will tell you exactly which tests produce different hashes. The harness is reusable too if you want to point it at your own canonicalization: @SatoshiImai this probably affects your design. Your reference implementations use RFC 8785 JCS, which handles key ordering but not float formatting. Two consumers running different SDKs will get the same key order from JCS but potentially different float representations. Chain hashes diverge silently. Might be worth a note in the proposal about numeric constraints, or at minimum flagging that JCS alone isn't sufficient for cross-SDK digest stability. |
|
@eason4kim-rocket following up on "happy to help test the conformance" — it's mergeable now, and it lands exactly on the 3-state model you called out. The battery is in the SEP working draft under sep/conformance, with the fixture at examples/hostile-server; the standalone version is hostile-facilitator (MIT, stdlib-only, no keys). It drives a client through each ambiguous outcome and counts how many times the effect actually executed for one logical request. Two of the seven modes are the ones you'd care about most, since they test the distinction you named:
If you run it against your pipeline, the comparative diff you mentioned would be genuinely useful to the SEP — a second real implementation's results are exactly the kind of evidence the motivation section wants before it goes for a sponsor. And if your compliance pipeline needs modes the payment-shaped battery doesn't cover yet (VAT/registry reads have their own ambiguous-outcome shapes), that's worth a mode, and I'd add it. |
Uh oh!
There was an error while loading. Please reload this page.
Pre-submission Checklist
Discussion Topic
Hi. I'm a system architect working in the financial sector in Tokyo.
I'd like to propose an audit extension for MCP tools.
SatoshiImai/mcp-audit-extension
Motivation
My motivation is to make MCP tools have the capability to report audit events to their host.
I know we already have SEP-3004. It has great standard. But it is strictly scoped to the outside of the boundary of the tools. I'd like to approach those inside the tools.
Because we, the financial sector, will need these audit declarations from tools in the near future. Regulations like the EU AI Act (Article 12) would ask us to manage those events.
The Prototype: Auditable MCP v0.1
So I designed Auditable MCP v0.1 to make tools that can report their audit events to their hosts.
And I tested this design by implementing by TS and Python to prove it works.
My reference implementations demonstrate the creation of identical, byte-level hash chains across languages by accounting for differences such as RFC 8785 canonicalization.
Request for Feedback
Could you give me some feedback?
Thank you.
Note: Core architecture and decisions are completely mine. But huge thanks to AI (Claude/Gemini) as my great sparring partners. They deeply sharpened my thinking, and helped with coding, edge-cases, and English polishing.
All reactions