The contract
For every guardedtools/call, the gateway:
- Canonicalizes
{actor, tool, resource, arguments, invocation_id}and computes a SHA-256call_id. - Asks NSR to prove the binary goal
can_execute(actor, call_id). - Requires
decision == "approved"and a self-containedverifiable_bundle. - Re-checks that bundle through
/v1/proofs/verifyand confirms its proof goal is that exactcan_execute(actor, call_id)obligation. - Only then forwards the original, unchanged arguments upstream.
- Attaches a
com.stateset/authorizationreceipt — the complete bundle and its SHA-256 hash — to the MCP result metadata.
unguardedTools
bypass the gate; everything else is guarded by default.
Step 4 is the step that matters and the one an obvious implementation gets
wrong. Checking that a valid approval came back is not enough — an approval
for some other call is still a valid approval. The gate binds the proof to
the exact obligation, so a proof cannot be moved from one call to another.
The fresh
invocation_id nonce closes the same hole across time: an old
approval cannot be replayed for a later execution of the same tool with the
same arguments.Configure
The policy names the upstream MCP server, the agent identity, which argument carries the resource, and the read-only tools allowed to bypass authorization.What you get back
A successful call carries its authorization with it. The receipt is the complete, independently verifiable bundle plus its hash, so an auditor reading your logs six months later can re-check that the refund was authorized — without asking NSR, and without trusting it. That is the difference between a system that logs that it checked and one that can prove what it checked.Next steps
Verified decisions
The
can_execute decision the gate asks for, and the proof it returns.Proof verification
The check in step 4, which you can also run offline.
NSR MCP server
Driving NSR itself from an agent.
MCP servers
The upstream servers a gate is placed in front of.