Skip to main content
Every guardrail that depends on an agent choosing to check first eventually meets an agent that doesn’t. The Agent Gate removes the choice: it sits between the agent and an existing MCP server, and forwards a tool call only after NSR has proved that exact call is authorized and the resulting proof verifies. The model never receives an unguarded mutating tool in the first place.
@stateset/agent-gate is not published to npm yet. The contract below is what it enforces; until the package ships, treat this page as the design you are integrating against rather than something you can npx today.

The contract

For every guarded tools/call, the gateway:
  1. Canonicalizes {actor, tool, resource, arguments, invocation_id} and computes a SHA-256 call_id.
  2. Asks NSR to prove the binary goal can_execute(actor, call_id).
  3. Requires decision == "approved" and a self-contained verifiable_bundle.
  4. Re-checks that bundle through /v1/proofs/verify and confirms its proof goal is that exact can_execute(actor, call_id) obligation.
  5. Only then forwards the original, unchanged arguments upstream.
  6. Attaches a com.stateset/authorization receipt — the complete bundle and its SHA-256 hash — to the MCP result metadata.
Denials, refusals, malformed proofs, unavailable authorization, timeouts and missing bundles all fail closed. Only tools listed in 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.
unguardedTools is the whole attack surface of this configuration. A tool on that list runs with no proof and no receipt, so it must be genuinely read-only — not “read-only in the cases we thought about”. get_order is fine; a tool that takes a filter and returns everything matching it is a data-exfiltration path, not a read.

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.